web-dev-qa-db-fra.com

Rappel à un fragment d'un DialogFragment

Question: Comment créer un rappel depuis un DialogFragment vers un autre fragment? Dans mon cas, l'activité impliquée doit être complètement ignorante du DialogFragment.

Considère que j'ai

public class MyFragment extends Fragment implements OnClickListener

Puis à un moment donné je pourrais faire

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

Où se trouve MyDialogFragment 

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

Mais rien ne garantit que l'auditeur sera présent si le DialogFragment s'interrompt et reprend son cycle de vie. Les seules garanties contenues dans un fragment sont celles transmises via un bundle via setArguments et getArguments.

Il existe un moyen de référencer l'activité si ce doit être l'auditeur:

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

Mais je ne veux pas que l'activité soit à l'écoute des événements, j'ai besoin d'un fragment. En réalité, il pourrait s'agir de n'importe quel objet Java implémentant OnClickListener.

Prenons l'exemple concret d'un fragment qui présente un AlertDialog via DialogFragment. Il a des boutons Oui/Non. Comment puis-je renvoyer ces pressions sur le fragment qui l'a créé?

137
eternalmatt

L'activité impliquée ignore complètement le DialogFragment.

Classe de fragment:

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;
        }
    }
}

}

Classe DialogFragment:

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

La solution TargetFragment ne semble pas être la meilleure option pour les fragments de dialogue, car elle peut créer IllegalStateException après que l'application ait été détruite et recréée. Dans ce cas, FragmentManager n'a pas pu trouver le fragment cible et vous obtiendrez une IllegalStateException avec un message comme celui-ci:

"Le fragment n'existe plus pour la clé Android: target_state: index 1"

Il semble que Fragment#setTargetFragment() ne soit pas destiné à la communication entre un fragment enfant et parent, mais plutôt à une communication entre des fragments frères et sœurs.

La méthode alternative consiste donc à créer des fragments de dialogue comme celui-ci en utilisant la variable ChildFragmentManager du fragment parent, plutôt que les activités FragmentManager:

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

Et en utilisant une interface, dans la méthode onCreate de la DialogFragment, vous pouvez obtenir le fragment parent:

@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");
    }
}

La seule chose qui reste à faire est d'appeler votre méthode de rappel après ces étapes.

Pour plus d'informations sur le problème, vous pouvez consulter le lien: https://code.google.com/p/Android/issues/detail?id=54520

57
Oguz Ozcan

Peut-être un peu tard, mais peut aider d'autres personnes avec la même question, comme je l'ai fait.

Vous pouvez utiliser setTargetFragment sur Dialog avant d'afficher et dans la boîte de dialogue, vous pouvez appeler getTargetFragment pour obtenir la référence.

31
Rui

J'ai suivi ces étapes simples pour faire ce genre de choses.

  1. Créez une interface comme DialogFragmentCallbackInterface avec une méthode telle que callBackMethod(Object data). Ce que vous appelez pour transmettre des données.
  2. Maintenant, vous pouvez implémenter une interface DialogFragmentCallbackInterface dans votre fragment comme MyFragment implements DialogFragmentCallbackInterface 
  3. Au moment de la création de DialogFragment, définissez votre fragment appelant MyFragment en tant que fragment cible ayant créé DialogFragment, utilisez myDialogFragment.setTargetFragment(this, 0) check setTargetFragment (fragment de fragment, int requestCode)

    MyDialogFragment dialogFrag = new MyDialogFragment();
    dialogFrag.setTargetFragment(this, 1); 
    
  4. Obtenez votre objet fragment cible dans votre DialogFragment en appelant getTargetFragment() et transcrivez-le en DialogFragmentCallbackInterface.Maintenant, vous pouvez utiliser cette interface pour envoyer des données à votre fragment.

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

    C'est tout fait! assurez-vous simplement que vous avez implémenté cette interface dans votre fragment.

28
vsvankhede

Le guide Communication avec d'autres fragments / indique que les fragments doivent communiquer par le biais de l'activité associée.

Souvent, vous voudrez qu'un fragment communique avec un autre, par exemple exemple pour changer le contenu basé sur un événement utilisateur. Tout La communication fragment à fragment se fait par le biais du .__ associé. Activité. Deux fragments ne doivent jamais communiquer directement.

19
Edward Brey

Vous devez définir un interface dans votre classe de fragment et implémenter cette interface dans son activité parente. Les détails sont décrits ici http://developer.Android.com/guide/components/fragments.html#EventCallbacks . Le code ressemblerait à:

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");
        }
    }
}

Activité:

public class MyActivity extends Activity implements OnArticleSelectedListener{

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

    }
    ...
}
11
James McCracken

Je faisais face à un problème similaire. La solution que j'ai trouvée était:

  1. Déclarez une interface dans votre DialogFragment exactement comme James McCracken l'a expliqué ci-dessus.

  2. Implémentez l'interface dans votre activité (pas fragmenté! Ce n'est pas une bonne pratique).

  3. À partir de la méthode de rappel de votre activité, appelez une fonction publique requise dans votre fragment, qui effectue le travail que vous souhaitez effectuer.

Ainsi, cela devient un processus en deux étapes: DialogFragment -> Activité puis Activité -> Fragment.

2

La manière correcte de définir un auditeur sur un fragment consiste à le définir lorsqu'il est attaché . Le problème que j'ai eu est que onAttachFragment () n'a jamais été appelé. Après quelques recherches, j'ai réalisé que j'avais utilisé getFragmentManager au lieu de getChildFragmentManager

Voici comment je le fais:

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

Attachez-le à onAttachFragment:

@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

Mis à jour:

J'ai créé une bibliothèque basée sur mon code Gist qui génère ces castings en utilisant @CallbackFragment et @Callback.

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

Et l'exemple vous donne l'exemple qui envoie un rappel d'un fragment à un autre fragment.

Ancienne réponse:

J'ai créé une BaseCallbackFragment et une annotation @FragmentCallback. Il étend actuellement Fragment, vous pouvez le changer en DialogFragment et fonctionnera. Il vérifie les implémentations dans l'ordre suivant: getTargetFragment ()> getParentFragment ()> context (activity).

Il vous suffit ensuite de l'étendre, de déclarer vos interfaces dans votre fragment et de lui donner l'annotation. Le fragment de base fera le reste. L'annotation comporte également un paramètre mandatory qui vous permet de déterminer si vous souhaitez forcer le fragment à implémenter le rappel.

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

J'obtiens le résultat de Fragment DashboardLiveWall (fragment appelant) à partir de Fragment LiveWallFilterFrter (fragment de réception).

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

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

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 retourne à l'appel d'un fragment comme

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

Selon la documentation officielle:

fragment # setTargetFragment

Cible facultative pour ce fragment. Cela peut être utilisé, par exemple, si ce fragment est démarré par un autre et que, lorsque cela est fait, veut rendre un résultat au premier. La cible définie ici est conservée d'une instance à l'autre via FragmentManager # putFragment.

fragment # getTargetFragment

Renvoie le fragment cible défini par setTargetFragment (Fragment, int).

Donc, vous pouvez faire ceci:

// 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

Je l'ai résolu de manière élégante avec RxAndroid. Recevez un observateur dans le constructeur de DialogFragment et abonnez-vous à observable et envoyez la valeur lorsque le rappel est appelé. Ensuite, dans votre fragment, créez une classe interne de Observer, créez une instance et transmettez-la au constructeur de DialogFragment. J'ai utilisé WeakReference dans l'observateur pour éviter les fuites de mémoire. Voici le 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() {

    }
}

Vous pouvez voir le code source ici: https://github.com/andresuarezz26/carpoolapp

0
Gerardo Suarez