Gibt es eine Methode, die wie Startfragment für Ergebnis funktioniert?

90

Ich habe derzeit ein Fragment in einer Überlagerung. Dies dient zur Anmeldung beim Dienst. In der Telefon-App sind alle Schritte, die ich im Overlay anzeigen möchte, eigene Bildschirme und Aktivitäten. Es gibt 3 Teile des Anmeldevorgangs und jeder hatte seine eigene Aktivität, die mit startActivityForResult () aufgerufen wurde.

Jetzt möchte ich dasselbe mit Fragmenten und einer Überlagerung tun. Die Überlagerung zeigt ein Fragment, das jeder Aktivität entspricht. Das Problem ist, dass diese Fragmente in einer Aktivität in der Honeycomb-API gehostet werden. Ich kann das erste Fragment zum Laufen bringen, aber dann muss ich ActivityForResult () starten, was nicht möglich ist. Gibt es etwas in der Art von startFragmentForResult (), bei dem ich ein neues Fragment starten kann und wenn es fertig ist, ein Ergebnis zum vorherigen Fragment zurückgibt?

CACuzcatlan
quelle

Antworten:

57

Alle Fragmente leben in Aktivitäten. Das Starten eines Fragments für ein Ergebnis macht wenig Sinn, da die Aktivität, in der es sich befindet, immer Zugriff darauf hat und umgekehrt. Wenn das Fragment ein Ergebnis weitergeben muss, kann es auf seine Aktivität zugreifen, das Ergebnis festlegen und beenden. Wenn Sie Fragmente in einer einzelnen Aktivität austauschen, können beide Fragmente weiterhin auf die Aktivität zugreifen, und alle Ihre Nachrichten können einfach durch die Aktivität geleitet werden.

Denken Sie daran, dass Sie immer eine Kommunikation zwischen einem Fragment und seiner Aktivität haben. Das Starten und Beenden mit einem Ergebnis ist der Mechanismus für die Kommunikation zwischen Aktivitäten. Die Aktivitäten können dann alle erforderlichen Informationen an ihre Fragmente delegieren.

LeffelMania
quelle
11
Wenn Sie ein Fragment von einem anderen laden, können Sie außerdem das Zielfragment festlegen und auf onActivityResultWunsch die Methode des übergeordneten Fragments aufrufen .
PJL
3
Ihre Antwort ist unvollständig, Fragmente verwenden einen Lebenszyklus wie Aktivitäten. Wir können also keine Argumente über Methoden übergeben, sondern sollten Bundles verwenden, um Werte zu übergeben. Selbst wenn das Fragment irgendwo einen Wert gesetzt hat, müssen wir wissen, wann er geendet hat. Sollten die vorherigen Fragmente den Wert erhalten, wenn sie gestartet / fortgesetzt werden? Das ist eine Idee. Es gibt jedoch keine geeignete Möglichkeit, den Wert zu speichern. Das Fragment kann von mehreren anderen Fragmenten / Aktivitäten aufgerufen werden.
Loenix
59

Wenn Sie möchten, gibt es einige Methoden für die Kommunikation zwischen Fragmenten.

setTargetFragment(Fragment fragment, int requestCode)
getTargetFragment()
getTargetRequestCode()

Mit diesen können Sie zurückrufen.

Fragment invoker = getTargetFragment();
if(invoker != null) {
    invoker.callPublicMethod();
}
nagoya0
quelle
Ich habe es nicht bestätigt, aber vielleicht funktioniert es. Daher sollten Sie sich um Speicherverluste kümmern, die durch Zirkelverweise durch Missbrauch von verursacht werden setTargetFragment().
Nagoya0
3
Beste Lösung bisher! Funktioniert hervorragend, wenn Sie eine Aktivität und einen Stapel Fragmente haben, bei denen der Versuch, das richtige Fragment für die Benachrichtigung zu finden, ein Albtraum wäre. Danke
Damien Praca
1
@ userSeven7s dies funktioniert nicht für Fall ersetzen , sollte aber für Fall verstecken funktionieren
Muhammad Babar
1
@ Nagoya0 macht es besser, wenn wir WeakReference für
Zielfragment verwenden
11

Wir können einfach dasselbe ViewModel zwischen Fragmenten teilen

SharedViewModel

import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel

class SharedViewModel : ViewModel() {

    val stringData: MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }

}

FirstFragment

import android.arch.lifecycle.Observer
import android.os.Bundle
import android.arch.lifecycle.ViewModelProviders
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

class FirstFragment : Fragment() {

    private lateinit var sharedViewModel: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        activity?.run {
            sharedViewModel = ViewModelProviders.of(activity).get(SharedViewModel::class.java)
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_first, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        sharedViewModel.stringData.observe(this, Observer { dateString ->
            // get the changed String
        })

    }

}

SecondFragment

import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGrou

class SecondFragment : Fragment() {

    private lateinit var sharedViewModel: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        activity?.run {
            sharedViewModel = ViewModelProviders.of(activity).get(SharedViewModel::class.java)
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_first, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        changeString()
    }

    private fun changeString() {
        sharedViewModel.stringData.value = "Test"
    }

}
Levon Petrosyan
quelle
2
Hallo Levon, ich denke, der obige Code wird nicht dieselbe Ansichtsmodellinstanz verwenden. Sie übergeben dieses (Fragment) für ViewModelProviders.of (this) .get (SharedViewModel :: class.java). Dadurch werden zwei separate Instanzen für Fragmente erstellt. Sie müssen die Aktivität ViewModelProviders.of (Aktivität) .get (SharedViewModel :: class.java)
Shailendra Patil
@ShailendraPatil Netter Fang, ich werde es jetzt beheben.
Levon Petrosyan
4

Meine 2 Cent.

Ich wechsle zwischen Fragmenten, indem ich ein altes Fragment mit einem neuen austausche, indem ich es ausblende und zeige / hinzufüge (vorhanden / neu). Diese Antwort ist also für Entwickler, die Fragmente wie ich verwenden.

Dann benutze ich die onHiddenChanged Methode, um zu wissen, dass das alte Fragment vom neuen zurückgeschaltet wurde. Siehe Code unten.

Bevor ich das neue Fragment verlasse, habe ich ein Ergebnis in einem globalen Parameter festgelegt, der vom alten Fragment abgefragt werden soll. Dies ist eine sehr naive Lösung.

@Override
public void onHiddenChanged(boolean hidden) {
    super.onHiddenChanged(hidden);
    if (hidden) return;
    Result result = Result.getAndReset();
    if (result == Result.Refresh) {
        refresh();
    }
}

public enum Result {
    Refresh;

    private static Result RESULT;

    public static void set(Result result) {
        if (RESULT == Refresh) {
            // Refresh already requested - no point in setting anything else;
            return;
        }
        RESULT = result;
    }

    public static Result getAndReset() {
        Result result = RESULT;
        RESULT = null;
        return result;
    }
}
AlikElzin-Kilaka
quelle
Was ist eine getAndReset()Methode?
EpicPandaForce
Wird nicht auch onResume()das erste Fragment aufgerufen, wenn das zweite entlassen wird?
PJ_Finnegan
4

Vor kurzem hat Google gerade eine neue Funktion hinzugefügt, die FragmentManagerdas gemacht hatFragmentManager , als zentraler Speicher für Fragmentergebnisse zu fungieren. Wir können die Daten leicht zwischen Fragmenten hin und her übertragen.

Startfragment.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Use the Kotlin extension in the fragment-ktx artifact
    setResultListener("requestKey") { key, bundle ->
        // We use a String here, but any type that can be put in a Bundle is supported
        val result = bundle.getString("bundleKey")
        // Do something with the result...
    }
}

Ein Fragment, das wir zurückhaben wollen.

button.setOnClickListener {
    val result = "result"
    // Use the Kotlin extension in the fragment-ktx artifact
    setResult("requestKey", bundleOf("bundleKey" to result))
}

Das Snippet stammt aus den offiziellen Dokumenten von Google. https://developer.android.com/training/basics/fragments/pass-data-between#kotlin

Bei den Daten dieser Antwort befindet sich diese Funktion noch im alphaStatus. Sie können es mit dieser Abhängigkeit ausprobieren.

androidx.fragment:fragment:1.3.0-alpha05
Boonya Kitpitak
quelle
2

In Ihrem Fragment können Sie getActivity () aufrufen. Dadurch erhalten Sie Zugriff auf die Aktivität, mit der das Fragment erstellt wurde. Von dort aus können Sie Ihre Anpassungsmethode aufrufen, um die Werte festzulegen oder die Werte zu übergeben.

Summierter Jain
quelle
1

Es gibt eine Android-Bibliothek - FlowR , mit der Sie Fragmente für Ergebnisse starten können.

Ein Fragment für das Ergebnis starten.

Flowr.open(RequestFragment.class)
    .displayFragmentForResults(getFragmentId(), REQUEST_CODE);

Die Behandlung führt zum aufrufenden Fragment.

@Override
protected void onFragmentResults(int requestCode, int resultCode, Bundle data) {
    super.onFragmentResults(requestCode, resultCode, data);

    if (requestCode == REQUEST_CODE) {
        if (resultCode == Activity.RESULT_OK) {
            demoTextView.setText("Result OK");
        } else {
            demoTextView.setText("Result CANCELED");
        }
    }
}

Festlegen des Ergebnisses im Fragment.

Flowr.closeWithResults(getResultsResponse(resultCode, resultData));
Ragunath Jawahar
quelle
1

Eine Lösung mit Schnittstellen (und Kotlin). Die Kernidee besteht darin, eine Rückrufschnittstelle zu definieren, sie in Ihre Aktivität zu implementieren und sie dann von Ihrem Fragment aus aufzurufen.

Erstellen Sie zunächst eine Schnittstelle ActionHandler:

interface ActionHandler {
    fun handleAction(actionCode: String, result: Int)
}

Rufen Sie dies als Nächstes von Ihrem Kind (in diesem Fall Ihrem Fragment) auf:

companion object {
    const val FRAGMENT_A_CLOSED = "com.example.fragment_a_closed"
}

fun closeFragment() {
    try {
        (activity as ActionHandler).handleAction(FRAGMENT_A_CLOSED, 1234)
    } catch (e: ClassCastException) {
        Timber.e("Calling activity can't get callback!")
    }
    dismiss()
}

Implementieren Sie dies schließlich in Ihrem übergeordneten Element, um den Rückruf zu erhalten (in diesem Fall Ihre Aktivität):

class MainActivity: ActionHandler { 
    override fun handleAction(actionCode: String, result: Int) {
        when {
            actionCode == FragmentA.FRAGMENT_A_CLOSED -> {
                doSomething(result)
            }
            actionCode == FragmentB.FRAGMENT_B_CLOSED -> {
                doSomethingElse(result)
            }
            actionCode == FragmentC.FRAGMENT_C_CLOSED -> {
                doAnotherThing(result)
            }
        }
    }
Jake Lee
quelle
0

Der einfachste Weg, Daten zurückzugeben, ist setArgument (). Zum Beispiel haben Sie fragment1, das fragment2 aufruft, das fragment3 aufruft, fragment1 -> framgnet2 -> fargement3

In Fragment1

public void navigateToFragment2() {
    if (fragmentManager == null) return;

    Fragment2 fragment = Fragment2.newInstance();
    String tag = "Fragment 2 here";
    fragmentManager.beginTransaction()
            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
            .add(R.id.flContent, fragment, tag)
            .addToBackStack(null)
            .commitAllowingStateLoss();
}

In Fragment2 nennen wir Fragment3 wie gewohnt

private void navigateToFragment3() {
    if (fragmentManager == null) return;
    Fragment3 fragment = new Fragment3();
    fragmentManager.beginTransaction()
            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
            .replace(R.id.flContent, fragment, tag)
            .addToBackStack(null)
            .commit();
}

Als wir unsere Aufgabe in Fragment3 beendet haben, rufen wir jetzt so auf:

FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
if (fragmentManager == null) return;
fragmentManager.popBackStack();
Bundle bundle = new Bundle();
bundle.putString("bundle_filter", "data");
fragmentManager.findFragmentByTag("Fragment 2 here").setArguments(bundle);

Jetzt können wir in fragment2 leicht Argumente aufrufen

@Override
public void onResume() {
    super.onResume();
    Bundle rgs = getArguments();
    if (args != null) 
        String data = rgs.getString("bundle_filter");
}
Kirk_hehe
quelle
Seien Sie vorsichtig mit diesem, es könnte in einigen Fällen funktionieren, aber wenn Ihr Fragment ursprüngliche Argumente hätte (in diesem Beispiel 'Fragment 2'), würde dies die ursprünglichen Argumente überschreiben, die zum Erstellen des Fragments verwendet wurden, was zu einem inkonsistenten Zustand führen könnte, wenn Das Fragment wird zerstört und neu erstellt.
Juan
0

Abhängig von Ihrer Architektur können Sie auch ein gemeinsames ViewModel zwischen den Fragmenten verwenden. In meinem Fall ist FragmentA ein Formular, und FragmentB ist eine Elementauswahlansicht, in der der Benutzer ein Element suchen und auswählen und im ViewModel speichern kann. Wenn ich dann zu FragmentA zurückkomme, sind die Informationen bereits gespeichert!

Arjun
quelle