wake-up-neo.com

Rückruf an ein Fragment aus einem DialogFragment

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

Betrachten Sie, ich habe

public class MyFragment extends Fragment implements OnClickListener

Dann irgendwann würde ich könnte machen

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

Wo sieht meinDialogFragment aus? 

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

Es kann jedoch nicht garantiert werden, dass der Listener in der Nähe ist, wenn DialogFragment angehalten wird und seinen Lebenszyklus fortsetzt. Die einzigen Garantien in einem Fragment sind die über setArguments und getArguments über ein Bundle übergebenen.

Es gibt eine Möglichkeit, auf die Aktivität zu verweisen, wenn es der Listener sein sollte:

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 hört, 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ückschicken, von dem sie erstellt wurden?

137
eternalmatt

Die betreffende Aktivität kennt das DialogFragment 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();
}
}
175
Piotr Ślesarew

Die TargetFragment-Lösung scheint nicht die beste Option für Dialogfragmente zu sein, da IllegalStateException erstellt werden kann, nachdem die Anwendung zerstört und erneut erstellt wurde. In diesem Fall konnte FragmentManager das Zielfragment nicht finden und Sie erhalten eine IllegalStateException mit einer Nachricht wie folgt:

Msgstr "Fragment existiert nicht mehr für Android: target_state: index 1"

Es scheint, als wäre Fragment#setTargetFragment() nicht für die Kommunikation zwischen einem untergeordneten und einem übergeordneten Fragment gedacht, sondern eher für die Kommunikation zwischen Geschwisterfragmenten.

Alternativ können Sie also Dialogfragmente wie folgt erstellen, indem Sie die ChildFragmentManager des übergeordneten Fragments verwenden, anstatt die Aktivitäten FragmentManager zu verwenden:

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

Durch die Verwendung einer Schnittstelle können Sie in der onCreate-Methode der DialogFragment das ü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");
    }
}

Jetzt müssen Sie nur noch Ihre Callback-Methode aufrufen.

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

57
Oguz Ozcan

Vielleicht etwas spät, aber vielleicht anderen Leuten mit der gleichen Frage wie ich helfen.

Sie können setTargetFragment für Dialog verwenden, bevor Sie sie anzeigen. Im Dialogfeld können Sie getTargetFragment aufrufen, um die Referenz zu erhalten.

31
Rui

Ich bin diesen einfachen Schritten gefolgt, um dieses Zeug zu machen.

  1. Erstellen Sie eine Schnittstelle wie DialogFragmentCallbackInterface mit einer Methode wie callBackMethod(Object data). Welches würden Sie anrufen, um Daten zu übergeben.
  2. Jetzt können Sie DialogFragmentCallbackInterface-Schnittstelle in Ihr Fragment wie MyFragment implements DialogFragmentCallbackInterface implementieren. 
  3. Zum Zeitpunkt der Erstellung von DialogFragment setzen Sie Ihr aufrufendes Fragment MyFragment als Zielfragment, das DialogFragment erstellt hat. myDialogFragment.setTargetFragment(this, 0) check setTargetFragment (Fragmentfragment, int requestCode)

    MyDialogFragment dialogFrag = new MyDialogFragment();
    dialogFrag.setTargetFragment(this, 1); 
    
  4. Holen Sie Ihr Zielfragmentobjekt in Ihre DialogFragment, indem Sie getTargetFragment() aufrufen und es in DialogFragmentCallbackInterface 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 nur sicher, dass Sie diese Schnittstelle in Ihrem Fragment implementiert haben.

28
vsvankhede

Der Mit anderen Fragmenten kommunizieren Guide sagt, dass die Fragmente durch die zugehörige Aktivität kommunizieren sollen.

Oft möchten Sie, dass ein Fragment mit einem anderen kommuniziert, z. B. Beispiel, um den Inhalt basierend auf einem Benutzerereignis zu ändern. Alles Die Fragment-zu-Fragment-Kommunikation erfolgt über das zugehörige Aktivität. Zwei Fragmente sollten niemals direkt kommunizieren.

19
Edward Brey

Sie sollten eine interface in 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:

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){

    }
    ...
}
11
James McCracken

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

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

  2. Implementieren Sie die Schnittstelle in Ihrer Aktivität (nicht fragmentieren! Das ist keine gute Praxis).

  3. Rufen Sie in der Callback-Methode in Ihrer Aktivität eine erforderliche öffentliche Funktion in Ihrem Fragment auf, die die gewünschte Aufgabe ausführt.

Es wird also ein zweistufiger Prozess: DialogFragment -> Aktivität und dann Aktivität -> Fragment

Der richtige Weg, um einen Listener auf ein Fragment zu setzen, besteht darin, dass es gesetzt wird, wenn es attach ist. Das Problem, das ich hatte, war, dass onAttachFragment () nie aufgerufen wurde. Nach einiger Untersuchung stellte ich fest, dass ich getFragmentManager anstelle von getChildFragmentManager verwendet hatte.

So mache ich es:

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() {

            }
        });
    }
}
1
user1354603

Aktualisierte:

Ich habe eine Bibliothek erstellt, die auf meinem Gist-Code basiert und diese Castings für Sie generiert, indem Sie @CallbackFragment und @Callback verwenden.

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

Und das Beispiel gibt Ihnen das Beispiel, in dem ein Callback von einem Fragment an ein anderes Fragment gesendet wird.

Alte Antwort:

Ich habe eine BaseCallbackFragment und eine Annotation @FragmentCallback gemacht. Es erweitert derzeit Fragment, Sie können es in DialogFragment ändern und funktionieren. Es prüft die Implementierungen in der folgenden Reihenfolge: getTargetFragment ()> getParentFragment ()> context (activity).

Dann müssen Sie es nur erweitern und Ihre Schnittstellen in Ihrem Fragment deklarieren und ihm die Anmerkung geben, und das Basisfragment erledigt den Rest. Die Annotation hat auch einen Parameter mandatory, mit dem Sie bestimmen können, ob Sie das Fragment zur Implementierung des Callbacks zwingen möchten.

public class EchoFragment extends BaseCallbackFragment {

    private FragmentInteractionListener mListener;

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

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

1
Arst

Ich bekomme 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();

woher

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 aufrufenden Fragment 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 {
                    }
                }
            }
        }
    }
1
Xar E Ahmer

Laut der offiziellen Dokumentation:

Fragment # setTargetFragment

Optionales Ziel für dieses Fragment. Dies kann zum Beispiel verwendet werden, wenn dieses Fragment von einem anderen Fragment gestartet wird und wenn dies erledigt ist, möchte es dem ersten ein Ergebnis zurückgeben. Das hier festgelegte Ziel wird über FragmentManager # putFragment instanzübergreifend beibehalten.

Fragment # getTargetFragment

Gibt das mit setTargetFragment (Fragment, int) gesetzte Zielfragment zurück.

So können Sie dies 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");
        }
    }
    ...
}
0
SuperYao

Ich habe das mit RxAndroid auf elegante Weise gelöst. Empfangen Sie einen Beobachter im Konstruktor von DialogFragment und setzen Sie ihn auf beobachtbare Werte, und drücken Sie den Wert, wenn der Callback aufgerufen wird. Erstellen Sie dann in Ihrem Fragment eine innere Klasse des Observer, erstellen Sie eine Instanz und übergeben Sie diese im Konstruktor von DialogFragment. Ich habe WeakReference im Observer verwendet, um Speicherlecks 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() {

    }
}

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

0
Gerardo Suarez