Rückruf auf ein Fragment aus einem DialogFragment

159

Frage: Wie erstellt man einen Rückruf von einem DialogFragment zu einem anderen Fragment? In meinem Fall sollte die betreffende Aktivität das DialogFragment überhaupt nicht kennen.

Betrachten Sie ich habe

public class MyFragment extends Fragment implements OnClickListener

Dann könnte ich irgendwann tun

DialogFragment dialogFrag = MyDialogFragment.newInstance(this);
dialogFrag.show(getFragmentManager, null);

Wo MyDialogFragment aussieht

protected OnClickListener listener;
public static DialogFragment newInstance(OnClickListener listener) {
    DialogFragment fragment = new DialogFragment();
    fragment.listener = listener;
    return fragment;
}

Es gibt jedoch keine Garantie dafür, dass der Listener verfügbar ist, wenn das DialogFragment angehalten und während seines Lebenszyklus fortgesetzt wird. Die einzigen Garantien in einem Fragment sind diejenigen, die über setArguments und getArguments durch ein Bundle übergeben werden.

Es gibt eine Möglichkeit, auf die Aktivität zu verweisen, wenn es sich um den Listener handeln soll:

public Dialog onCreateDialog(Bundle bundle) {
    OnClickListener listener = (OnClickListener) getActivity();
    ....
    return new AlertDialog.Builder(getActivity())
        ........
        .setAdapter(adapter, listener)
        .create();
}

Aber ich möchte nicht, dass die Aktivität auf Ereignisse wartet, ich brauche ein Fragment. Wirklich, es könnte jedes Java-Objekt sein, das OnClickListener implementiert.

Betrachten Sie das konkrete Beispiel eines Fragments, das einen AlertDialog über DialogFragment darstellt. Es hat Ja / Nein-Tasten. Wie kann ich diese Tastendrücke an das Fragment zurücksenden, das sie erstellt hat?

ewigmatt
quelle
Sie haben erwähnt: "Es gibt jedoch keine Garantie dafür, dass der Listener verfügbar ist, wenn das DialogFragment angehalten wird und seinen Lebenszyklus fortsetzt." Ich dachte, der Fragmentzustand wird während onDestroy () zerstört? Sie müssen Recht haben, aber ich bin nur ein bisschen verwirrt, wie ich den Fragmentstatus jetzt verwenden soll. Wie reproduziere ich das von Ihnen erwähnte Problem, der Hörer ist nicht da?
Sean
Ich verstehe nicht, warum Sie nicht einfach OnClickListener listener = (OnClickListener) getParentFragment();in DialogFragment verwenden können und Ihr Hauptfragment die Schnittstelle wie ursprünglich implementiert.
Kiruwka
Hier ist eine Antwort auf eine nicht verwandte Frage, aber sie zeigt Ihnen, wie dies auf saubere Weise gemacht wird. Stackoverflow.com/questions/28620026/…
user2288580

Antworten:

190

Die beteiligten Aktivitäten kennen das DialogFragment überhaupt nicht.

Fragmentklasse:

public class MyFragment extends Fragment {
int mStackLevel = 0;
public static final int DIALOG_FRAGMENT = 1;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    if (savedInstanceState != null) {
        mStackLevel = savedInstanceState.getInt("level");
    }
}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("level", mStackLevel);
}

void showDialog(int type) {

    mStackLevel++;

    FragmentTransaction ft = getActivity().getFragmentManager().beginTransaction();
    Fragment prev = getActivity().getFragmentManager().findFragmentByTag("dialog");
    if (prev != null) {
        ft.remove(prev);
    }
    ft.addToBackStack(null);

    switch (type) {

        case DIALOG_FRAGMENT:

            DialogFragment dialogFrag = MyDialogFragment.newInstance(123);
            dialogFrag.setTargetFragment(this, DIALOG_FRAGMENT);
            dialogFrag.show(getFragmentManager().beginTransaction(), "dialog");

            break;
    }
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch(requestCode) {
            case DIALOG_FRAGMENT:

                if (resultCode == Activity.RESULT_OK) {
                    // After Ok code.
                } else if (resultCode == Activity.RESULT_CANCELED){
                    // After Cancel code.
                }

                break;
        }
    }
}

}

DialogFragment-Klasse:

public class MyDialogFragment extends DialogFragment {

public static MyDialogFragment newInstance(int num){

    MyDialogFragment dialogFragment = new MyDialogFragment();
    Bundle bundle = new Bundle();
    bundle.putInt("num", num);
    dialogFragment.setArguments(bundle);

    return dialogFragment;

}

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {

    return new AlertDialog.Builder(getActivity())
            .setTitle(R.string.ERROR)
            .setIcon(android.R.drawable.ic_dialog_alert)
            .setPositiveButton(R.string.ok_button,
                    new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int whichButton) {
                            getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, getActivity().getIntent());
                        }
                    }
            )
            .setNegativeButton(R.string.cancel_button, new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
                    getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_CANCELED, getActivity().getIntent());
                }
            })
            .create();
}
}
Piotr Ślesarew
quelle
100
Ich denke der Schlüssel hier ist setTargetFragmentund getTargetFragment. Die Verwendung von onActivityResultist etwas unklar. Es ist wahrscheinlich besser, Ihre eigene spezifische Methode im Fragment-Aufrufer zu deklarieren und diese zu verwenden, anstatt onActivityResult neu zu definieren. Aber an diesem Punkt ist alles Semantik.
ewigmatt
2
Stack Level Variable wird nicht verwendet?
Anzeigename
6
Wird dies eine Rotation der Konfigurationsänderung überleben?
Maxrunner
3
Benutzte dies. Anmerkungen: Die Stapelebene war nicht erforderlich, um die Rotation oder den Schlaf zu überleben. Anstelle von onActivityResult implementiert mein Fragment DialogResultHandler # handleDialogResult (eine von mir erstellte Schnittstelle). @myCode, wäre sehr hilfreich, um einen im Dialogfeld ausgewählten Wert anzuzeigen, der der Absicht hinzugefügt wird, und dann in Ihrem onActivityResult zu lesen. Absichten sind für Anfänger unklar.
Chris Betti
7
@eternalmatt, Ihr Einwand ist völlig vernünftig, aber ich denke, der Wert von onActivityResult () besteht darin, dass es garantiert auf jedem Fragment vorhanden ist, sodass jedes Fragment als übergeordnetes Fragment verwendet werden kann. Wenn Sie eine eigene Schnittstelle erstellen und vom übergeordneten Fragment implementieren lassen, kann das untergeordnete Element nur von Eltern verwendet werden, die diese Schnittstelle implementieren. Das Koppeln des Kindes an diese Schnittstelle kann Sie möglicherweise verfolgen, wenn Sie das Kind später häufiger verwenden. Die Verwendung der "integrierten" onActivityResult () -Schnittstelle erfordert keine zusätzliche Kopplung, sodass Sie etwas mehr Flexibilität erhalten.
Dalbergia
78

Die TargetFragment-Lösung scheint nicht die beste Option für Dialogfragmente zu sein, da sie möglicherweise erstellt wird, IllegalStateExceptionnachdem die Anwendung zerstört und neu erstellt wurde. In diesem Fall FragmentManagerkonnte das Zielfragment nicht gefunden werden und Sie erhalten IllegalStateExceptioneine Meldung wie die folgende:

"Fragment existiert nicht mehr für Schlüssel android: target_state: index 1"

Es scheint Fragment#setTargetFragment()nicht für die Kommunikation zwischen einem Kind und einem Elternfragment gedacht zu sein, sondern für die Kommunikation zwischen Geschwisterfragmenten.

Eine alternative Möglichkeit besteht darin, Dialogfragmente wie dieses mithilfe ChildFragmentManagerdes übergeordneten Fragments zu erstellen , anstatt die folgenden Aktivitäten zu verwenden FragmentManager:

dialogFragment.show(ParentFragment.this.getChildFragmentManager(), "dialog_fragment");

Und mithilfe einer Schnittstelle können Sie in der onCreateMethode von DialogFragmentdas übergeordnete Fragment erhalten:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    try {
        callback = (Callback) getParentFragment();
    } catch (ClassCastException e) {
        throw new ClassCastException("Calling fragment must implement Callback interface");
    }
}

Nach diesen Schritten müssen Sie nur noch Ihre Rückrufmethode aufrufen.

Weitere Informationen zu diesem Problem finden Sie unter folgendem Link: https://code.google.com/p/android/issues/detail?id=54520

Oguz Ozcan
quelle
2
Dies funktioniert auch mit dem in API 23 eingeführten onAttach (Kontextkontext).
Santa Teclado
1
Dies sollte die akzeptierte Antwort sein. Die derzeit akzeptierte Antwort ist fehlerhaft und Fragmente sind nicht dazu gedacht, so verwendet zu werden.
NecipAllef
3
@AhmadFadli Problem hier ist, den richtigen Kontext (übergeordnet) für die Kommunikation zwischen Fragmenten zu erhalten. Wenn Sie das Dialogfragment als Kind der Aktivität verwenden, sollte es keine Verwirrung geben. FragmentManager of Activity und getActivity () zum Abrufen von Rückrufen reichen aus.
Oguz Ozcan
1
Dies sollte die akzeptierte Antwort sein. Es ist klar erklärt und detailliert, es ist nicht nur Code, der auf Menschen geworfen wird.
Vince
1
@lukecross ParentFragment ist das Fragment, das das DialogFragment erstellt (das show () aufruft). Es sieht jedoch so aus, als ob childFragmentManager die Neukonfiguration / Bildschirmrotation nicht überlebt ...
iwat0qs
34

Ich habe diese einfachen Schritte befolgt, um dieses Zeug zu machen.

  1. Erstellen Sie eine Schnittstelle wie DialogFragmentCallbackInterfacemit einer Methode wie callBackMethod(Object data). Welche würden Sie anrufen, um Daten zu übergeben.
  2. Jetzt können Sie DialogFragmentCallbackInterfaceSchnittstelle in Ihrem Fragment wie implementierenMyFragment implements DialogFragmentCallbackInterface
  3. Zum Zeitpunkt der DialogFragmentErstellung legen Sie Ihr aufrufendes Fragment MyFragmentals Zielfragment fest , das DialogFragmentmithilfe von myDialogFragment.setTargetFragment(this, 0)check setTargetFragment (Fragmentfragment, int requestCode) erstellt wurde.

    MyDialogFragment dialogFrag = new MyDialogFragment();
    dialogFrag.setTargetFragment(this, 1); 
  4. Holen Sie sich Ihr Zielfragmentobjekt in Ihr Objekt, DialogFragmentindem Sie es aufrufen getTargetFragment()und in DialogFragmentCallbackInterface.Nun umwandeln. Jetzt können Sie diese Schnittstelle verwenden, um Daten an Ihr Fragment zu senden.

    DialogFragmentCallbackInterface callback = 
               (DialogFragmentCallbackInterface) getTargetFragment();
    callback.callBackMethod(Object data);

    Das ist alles erledigt! Stellen Sie einfach sicher, dass Sie diese Schnittstelle in Ihrem Fragment implementiert haben.

Vijay Vankhede
quelle
4
Dies sollte die beste Antwort sein. Gute Antwort.
Md. Sajedul Karim
Stellen Sie außerdem sicher, dass Sie für Quell- und Zielfragmente denselben Fragmentmanager verwenden, da sonst getTargetFragment nicht funktioniert. Wenn Sie also childFragmentManager verwenden, funktioniert dies nicht, da das Quellfragment nicht vom untergeordneten Fragmentmanager festgeschrieben wird. Es ist am besten, sich diese beiden Fragmente als Geschwisterfragmente und nicht als Eltern / Kind-Fragmente vorzustellen.
Thupten
Ehrlich gesagt ist es besser, nur das Zielfragmentmuster zu verwenden, wenn zwischen zwei Geschwisterfragmenten kommuniziert wird. Wenn Sie keinen Listener haben, vermeiden Sie, dass das Fragment1 versehentlich in Fragment2 verloren geht. Verwenden Sie bei Verwendung des Zielfragments keinen Listener / Callback. Nur verwenden onActivityResult(request code, resultcode, intent), um das Ergebnis an fragment1 zurückzugeben. Von fragment1, setTargetFragment()und von fragment2, Verwendung getTargetFragment(). Wenn Sie Eltern / Kind-Fragment zum Fragmentieren oder Aktivität zum Fragmentieren verwenden, können Sie Listener oder Rückruf verwenden, da keine Gefahr besteht, dass Eltern im Kind-Fragment verloren gehen.
Thupten
@Thupten Wenn Sie "Leak" sagen, meinen Sie damit "Memory Leak" oder "Leak Implementation Details"? Ich glaube nicht, dass Vijays Antwort mehr Speicher verliert als die Verwendung von onActivityResulty als Rückgabewert. Beide Muster beziehen sich weiterhin auf das Zielfragment. Wenn Sie damit meinen, dass Implementierungsdetails verloren gehen, ist sein Muster meiner Meinung nach sogar noch besser als bei onActivityResult. Die Rückrufmethode ist explizit (wenn richtig benannt). Wenn alles, was Sie zurückbekommen, in Ordnung und ABGESAGT ist, muss das erste Fragment interpretieren, was diese bedeuten.
tir38
34

Vielleicht etwas spät, kann aber anderen Menschen bei der gleichen Frage helfen wie ich.

Sie können setTargetFragmenton Dialogvor dem Anzeigen verwenden und im Dialogfeld aufrufen getTargetFragment, um die Referenz abzurufen.

Rui
quelle
Hier ist eine Antwort auf eine andere Frage, aber sie gilt auch für Ihre Frage und ist eine saubere Lösung: stackoverflow.com/questions/28620026/…
user2288580
IllegalStateException für mich
Luke Cross
19

Der Leitfaden Kommunikation mit anderen Fragmenten besagt, dass die Fragmente über die zugehörige Aktivität kommunizieren sollten .

Oft möchten Sie, dass ein Fragment mit einem anderen kommuniziert, um beispielsweise den Inhalt basierend auf einem Benutzerereignis zu ändern. Die gesamte Kommunikation von Fragment zu Fragment erfolgt über die zugehörige Aktivität. Zwei Fragmente sollten niemals direkt kommunizieren.

Edward Brey
quelle
1
Was ist mit inneren Fragmenten, dh wie sollte ein Fragment innerhalb eines anderen Fragments
Ravi
@ Ravi: Jedes Fragment sollte mit der Aktivität kommunizieren, die allen Fragmenten gemeinsam ist, indem getActivity () aufgerufen wird .
Edward Brey
1
@Chris: Wenn Fragmente eine fortlaufende Kommunikation benötigen, definieren Sie eine Schnittstelle für jedes zu implementierende Fragment. Die Aufgabe der Aktivität beschränkt sich dann darauf, Fragmente mit Schnittstellenzeigern auf ihre Gegenstückfragmente zu versehen. Danach können Fragmente sicher "direkt" über die Schnittstellen kommunizieren.
Edward Brey
3
Ich denke, dass mit der Erweiterung der Verwendung von Fragmenten die ursprüngliche Idee, keine direkte Fragmentkommunikation zu verwenden, zusammenbricht. Beispielsweise fungiert in einer Navigationsschublade jedes unmittelbare untergeordnete Fragment der Aktivität grob als Aktivität. Wenn also ein Fragment wie ein Dialogfragment über die Aktivität kommuniziert, beeinträchtigt dies die Lesbarkeit / Flexibilität der IMO. Tatsächlich scheint es keine gute Möglichkeit zu geben, Dialogfragmente zu kapseln, damit sie sowohl mit Aktivitäten als auch mit Fragmenten auf wiederverwendbare Weise arbeiten können.
Sam
16
Ich weiß, dass dies alt ist, aber falls jemand anderes hierher kommt, habe ich das Gefühl, dass der in diesem Dokument behandelte Fall nicht zutrifft, wenn ein Fragment die Logik "besitzt", mit der die Erstellung und Verwaltung des DialogFragments bestimmt wird. Es ist seltsam, eine Reihe von Verbindungen vom Fragment zur Aktivität herzustellen, wenn die Aktivität nicht einmal sicher ist, warum ein Dialog erstellt wird oder unter welchen Bedingungen er verworfen werden sollte. Außerdem ist das DialogFragment sehr einfach und dient nur dazu, den Benutzer zu benachrichtigen und möglicherweise eine Antwort zu erhalten.
Chris
12

Sie sollten eine interfacein Ihrer Fragmentklasse definieren und diese Schnittstelle in ihrer übergeordneten Aktivität implementieren. Die Details finden Sie hier http://developer.android.com/guide/components/fragments.html#EventCallbacks . Der Code würde ähnlich aussehen wie:

Fragment:

public static class FragmentA extends DialogFragment {

    OnArticleSelectedListener mListener;

    // Container Activity must implement this interface
    public interface OnArticleSelectedListener {
        public void onArticleSelected(Uri articleUri);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (OnArticleSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
        }
    }
}

Aktivität:

public class MyActivity extends Activity implements OnArticleSelectedListener{

    ...
    @Override
    public void onArticleSelected(Uri articleUri){

    }
    ...
}
James McCracken
quelle
1
Ich denke, Sie haben die Dokumente zu schnell überflogen. Beide Codesegmente sind FragmentAund er geht davon aus, dass eine Aktivität ein ist OnArticleSelectedListener, nicht das Fragment, das ihn gestartet hat.
ewig matt
2
Ich würde überlegen, was Sie versuchen, schlecht zu üben. Die Android-Richtlinien empfehlen, dass die gesamte Kommunikation von Fragment zu Fragment über die Aktivität erfolgt (per developer.android.com/training/basics/fragments/… ). Wenn Sie wirklich möchten, dass alles in MyFragmentIhnen erledigt wird , können Sie zu einem regulären wechselnAlertDialog
James McCracken
1
Ich denke, die Sorge, dass die Fragmente direkt miteinander sprechen, besteht darin, dass in einigen Layouts möglicherweise nicht alle Fragmente geladen werden und es, wie im Beispiel gezeigt, erforderlich sein kann, das Fragment einzuschalten. Ich denke nicht, dass Bedenken berechtigt sind, wenn es darum geht, ein Dialogfragment aus einem Fragment heraus zu starten.
Ich habe dies für meine Aktivitäten implementiert. Frage: Kann diese Lösung so erweitert werden, dass ein Fragment diesen Dialog instanziieren kann?
Bill Mote
1
Dies ist aus architektonischer Sicht eine gute Praxis und sollte als solche die akzeptierte Antwort sein. Die Verwendung von onActivityResult führt zu Spaghetti-Architektur
Bruno Carrier
3

Die richtige Methode zum Festlegen eines Listeners für ein Fragment besteht darin, es beim Anhängen festzulegen . Das Problem, das ich hatte, war, dass onAttachFragment () nie aufgerufen wurde. Nach einigen Nachforschungen stellte ich fest, dass ich getFragmentManager anstelle von getChildFragmentManager verwendet hatte

So mache ich das:

MyDialogFragment dialogFragment = MyDialogFragment.newInstance("title", "body");
dialogFragment.show(getChildFragmentManager(), "SOME_DIALOG");

Fügen Sie es in onAttachFragment hinzu:

@Override
public void onAttachFragment(Fragment childFragment) {
    super.onAttachFragment(childFragment);

    if (childFragment instanceof MyDialogFragment) {
        MyDialogFragment dialog = (MyDialogFragment) childFragment;
        dialog.setListener(new MyDialogFragment.Listener() {
            @Override
            public void buttonClicked() {

            }
        });
    }
}
user1354603
quelle
3

Laut offizieller Dokumentation:

Fragment # setTargetFragment

Optionales Ziel für dieses Fragment. Dies kann beispielsweise verwendet werden, wenn dieses Fragment von einem anderen gestartet wird und wenn dies erledigt ist, dem ersten ein Ergebnis zurückgegeben werden soll. Das hier festgelegte Ziel wird über FragmentManager # putFragment instanzübergreifend beibehalten.

Fragment # getTargetFragment

Gibt das von setTargetFragment (Fragment, int) festgelegte Zielfragment zurück.

So können Sie Folgendes tun:

// In your fragment

public class MyFragment extends Fragment implements OnClickListener {
    private void showDialog() {
        DialogFragment dialogFrag = MyDialogFragment.newInstance(this);
        // Add this
        dialogFrag.setTargetFragment(this, 0);
        dialogFrag.show(getFragmentManager, null);
    }
    ...
}

// then

public class MyialogFragment extends DialogFragment {
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        // Then get it
        Fragment fragment = getTargetFragment();
        if (fragment instanceof OnClickListener) {
            listener = (OnClickListener) fragment;
        } else {
            throw new RuntimeException("you must implement OnClickListener");
        }
    }
    ...
}
SUPERYAO
quelle
kannst du deine erklären
Yilmaz
In diesem Fall müssen wir den Verweis "MyFragment" an "MyialogFragment" übergeben, und "Fragment" stellt die Methode dafür bereit. Ich habe die Beschreibung des offiziellen Dokuments hinzugefügt, es sollte klarer sagen als ich.
SUPERYAO
2

Ich hatte ein ähnliches Problem. Die Lösung, die ich herausfand, war:

  1. Deklarieren Sie eine Schnittstelle in Ihrem DialogFragment, wie James McCracken oben erklärt hat.

  2. Implementieren Sie die Schnittstelle in Ihrer Aktivität (kein Fragment! Dies ist keine gute Vorgehensweise).

  3. Rufen Sie über die Rückrufmethode in Ihrer Aktivität eine erforderliche öffentliche Funktion in Ihrem Fragment auf, die den gewünschten Job ausführt.

Somit wird es ein zweistufiger Prozess: DialogFragment -> Aktivität und dann Aktivität -> Fragment

Shailesh Mani Pandey
quelle
1

Ich erhalte ein Ergebnis für Fragment DashboardLiveWall (aufrufendes Fragment) von Fragment LiveWallFilterFragment (empfangendes Fragment).

 LiveWallFilterFragment filterFragment = LiveWallFilterFragment.newInstance(DashboardLiveWall.this ,"");

 getActivity().getSupportFragmentManager().beginTransaction(). 
 add(R.id.frame_container, filterFragment).addToBackStack("").commit();

wo

public static LiveWallFilterFragment newInstance(Fragment targetFragment,String anyDummyData) {
        LiveWallFilterFragment fragment = new LiveWallFilterFragment();
        Bundle args = new Bundle();
        args.putString("dummyKey",anyDummyData);
        fragment.setArguments(args);

        if(targetFragment != null)
            fragment.setTargetFragment(targetFragment, KeyConst.LIVE_WALL_FILTER_RESULT);
        return fragment;
    }

setResult zurück zum Aufrufen von Fragmenten wie

private void setResult(boolean flag) {
        if (getTargetFragment() != null) {
            Bundle bundle = new Bundle();
            bundle.putBoolean("isWorkDone", flag);
            Intent mIntent = new Intent();
            mIntent.putExtras(bundle);
            getTargetFragment().onActivityResult(getTargetRequestCode(),
                    Activity.RESULT_OK, mIntent);
        }
    }

onActivityResult

@Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (resultCode == Activity.RESULT_OK) {
            if (requestCode == KeyConst.LIVE_WALL_FILTER_RESULT) {

                Bundle bundle = data.getExtras();
                if (bundle != null) {

                    boolean isReset = bundle.getBoolean("isWorkDone");
                    if (isReset) {

                    } else {
                    }
                }
            }
        }
    }
Zar E Ahmer
quelle
1

Aktualisiert:

Ich habe eine Bibliothek basierend auf meinem Kerncode erstellt, die diese Castings mit @CallbackFragmentund für Sie generiert @Callback.

https://github.com/zeroarst/callbackfragment .

Das Beispiel zeigt das Beispiel, in dem ein Rückruf von einem Fragment an ein anderes Fragment gesendet wird.

Alte Antwort:

Ich habe eine BaseCallbackFragmentund Anmerkung gemacht @FragmentCallback. Es wird derzeit erweitert Fragment, Sie können es ändern DialogFragmentund es wird funktionieren. Es überprüft die Implementierungen in der folgenden Reihenfolge: getTargetFragment ()> getParentFragment ()> context (Aktivität).

Dann müssen Sie es nur noch erweitern und Ihre Schnittstellen in Ihrem Fragment deklarieren und ihm die Anmerkung geben, und das Basisfragment erledigt den Rest. Die Anmerkung enthält auch einen Parameter, mandatorymit dem Sie bestimmen können, ob Sie das Fragment zwingen möchten, den Rückruf zu implementieren.

public class EchoFragment extends BaseCallbackFragment {

    private FragmentInteractionListener mListener;

    @FragmentCallback
    public interface FragmentInteractionListener {
        void onEcho(EchoFragment fragment, String echo);
    }
}

https://gist.github.com/zeroarst/3b3f32092d58698a4568cdb0919c9a93

Arst
quelle
1

Kotlin Jungs hier gehen wir!

Das Problem, das wir haben, ist, dass wir eine Aktivität erstellt haben MainActivity, für diese Aktivität ein Fragment erstellt haben FragmentAund jetzt ein Dialogfragment zusätzlich zum FragmentAAufruf erstellen möchten FragmentB. Wie bekommen wir die Ergebnisse von FragmentBzurück zu FragmentAohne durchzugehen MainActivity?

Hinweis:

  1. FragmentAist ein Kinderfragment von MainActivity. Um die in erstellten Fragmente zu verwalten, verwenden FragmentAwir das, childFragmentManagerwas das tut!
  2. FragmentAist ein übergeordnetes Fragment von FragmentB, auf das wir FragmentAvon innen zugreifen FragmentBwerden parenFragment.

Having said that, innen FragmentA,

class FragmentA : Fragment(), UpdateNameListener {
    override fun onSave(name: String) {
        toast("Running save with $name")
    }

    // call this function somewhere in a clickListener perhaps
    private fun startUpdateNameDialog() {
        FragmentB().show(childFragmentManager, "started name dialog")
    }
}

Hier ist das Dialogfragment FragmentB.

class FragmentB : DialogFragment() {

    private lateinit var listener: UpdateNameListener

    override fun onAttach(context: Context) {
        super.onAttach(context)
        try {
            listener = parentFragment as UpdateNameListener
        } catch (e: ClassCastException) {
            throw ClassCastException("$context must implement UpdateNameListener")
        }
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return activity?.let {
            val builder = AlertDialog.Builder(it)
            val binding = UpdateNameDialogFragmentBinding.inflate(LayoutInflater.from(context))
            binding.btnSave.setOnClickListener {
                val name = binding.name.text.toString()
                listener.onSave(name)
                dismiss()
            }
            builder.setView(binding.root)
            return builder.create()
        } ?: throw IllegalStateException("Activity can not be null")
    }
}

Hier ist die Schnittstelle, die die beiden verbindet.

interface UpdateNameListener {
    fun onSave(name: String)
}

Das ist es.

Ssenyonjo
quelle
1
Ich habe dieses Dokument befolgt: developer.android.com/guide/topics/ui/dialogs und es hat nicht funktioniert. Ich danke dir sehr. Ich hoffe, dass diese Parentfragment-Sache jedes Mal wie erwartet funktioniert :)
UmutTekin
1
Vergessen Sie nicht, den Listener in onDetach auf null zu setzen :)
BekaBot
@BekaBot Danke für den Kommentar. Ich habe einige Nachforschungen angestellt und es scheint, dass es nicht notwendig ist, die Zuhörer zu schließen. stackoverflow.com/a/37031951/10030693
Ssenyonjo
0

Ich habe dies auf elegante Weise mit RxAndroid gelöst. Empfangen Sie einen Beobachter im Konstruktor des DialogFragments und abonnieren Sie Observable und drücken Sie den Wert, wenn der Rückruf aufgerufen wird. Erstellen Sie dann in Ihrem Fragment eine innere Klasse des Beobachters, erstellen Sie eine Instanz und übergeben Sie sie im Konstruktor des DialogFragments. Ich habe WeakReference im Observer verwendet, um Speicherverluste zu vermeiden. Hier ist der Code:

BaseDialogFragment.java

import java.lang.ref.WeakReference;

import io.reactivex.Observer;

public class BaseDialogFragment<O> extends DialogFragment {

    protected WeakReference<Observer<O>> observerRef;

    protected BaseDialogFragment(Observer<O> observer) {
        this.observerRef = new WeakReference<>(observer);
   }

    protected Observer<O> getObserver() {
    return observerRef.get();
    }
}

DatePickerFragment.java

public class DatePickerFragment extends BaseDialogFragment<Integer>
    implements DatePickerDialog.OnDateSetListener {


public DatePickerFragment(Observer<Integer> observer) {
    super(observer);
}

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    // Use the current date as the default date in the picker
    final Calendar c = Calendar.getInstance();
    int year = c.get(Calendar.YEAR);
    int month = c.get(Calendar.MONTH);
    int day = c.get(Calendar.DAY_OF_MONTH);

    // Create a new instance of DatePickerDialog and return it
    return new DatePickerDialog(getActivity(), this, year, month, day);
}

@Override
public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {
        if (getObserver() != null) {
            Observable.just(month).subscribe(getObserver());
        }
    }
}

MyFragment.java

//Show the dialog fragment when the button is clicked
@OnClick(R.id.btn_date)
void onDateClick() {
    DialogFragment newFragment = new DatePickerFragment(new OnDateSelectedObserver());
    newFragment.show(getFragmentManager(), "datePicker");
}
 //Observer inner class
 private class OnDateSelectedObserver implements Observer<Integer> {

    @Override
    public void onSubscribe(Disposable d) {

    }

    @Override
    public void onNext(Integer integer) {
       //Here you invoke the logic

    }

    @Override
    public void onError(Throwable e) {

    }

    @Override
    public void onComplete() {

    }
}

Sie können den Quellcode hier sehen: https://github.com/andresuarezz26/carpoolingapp

Gerardo Suarez
quelle
1
Das Lustige an Android ist, dass es so etwas wie den Lebenszyklus gibt. Das Basisfragment oder Dialogfragment muss in der Lage sein, den Status (und seine Verbindung) über Lebenszyklusereignisse hinweg beizubehalten. Rückrufe oder Beobachter können nicht serialisiert werden und haben daher hier das gleiche Problem.
GDanger