Rappel d'un fragment à partir d'un DialogFragment

159

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

Considérez 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);

À quoi ressemble MyDialogFragment

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

Mais il n'y a aucune garantie que l'écouteur sera là si le DialogFragment s'arrête et reprend tout au long de son cycle de vie. Les seules garanties d'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é écoute les événements, j'ai besoin d'un fragment. Vraiment, il peut 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 boutons au fragment qui les a créés?

éternelmatt
la source
Vous avez mentionné "mais il n'y a aucune garantie que l'auditeur sera là si le DialogFragment s'arrête et reprend tout au long de son cycle de vie." Je pensais que l'état du fragment était détruit pendant onDestroy ()? Vous devez avoir raison, mais je ne sais pas trop comment utiliser l'état Fragment maintenant. Comment reproduire le problème que vous avez mentionné, l'auditeur n'est pas là?
Sean
Je ne vois pas pourquoi vous ne pouvez pas simplement utiliser OnClickListener listener = (OnClickListener) getParentFragment();DialogFragment à la place, et votre Fragment principal implémente l'interface comme vous l'avez fait à l'origine.
kiruwka
Voici une réponse à une question sans rapport, mais cela vous montre comment cela est fait de manière propre stackoverflow.com/questions/28620026
...

Réponses:

190

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();
}
}
Piotr Ślesarew
la source
100
Je pense que la clé ici est setTargetFragmentet getTargetFragment. L'utilisation de onActivityResultest un peu floue. Il serait probablement préférable de déclarer votre propre méthode spécifique dans l'appelant Fragment et de l'utiliser au lieu de réutiliser onActivityResult. Mais c'est toute la sémantique à ce stade.
éternelmatt
2
la variable de niveau de pile n'est pas utilisée?
Afficher le nom du
6
cela survivra-t-il à une rotation de changement de configuration?
Maxrunner
3
Utilisé ceci. Notes: le niveau de pile n'était pas nécessaire pour survivre à la rotation ou au sommeil. Au lieu de onActivityResult, mon fragment implémente DialogResultHandler # handleDialogResult (une interface que j'ai créée). @myCode, serait très utile pour afficher une valeur sélectionnée dans la boîte de dialogue ajoutée à l'intention, puis la lire dans votre onActivityResult. Les intentions ne sont pas claires pour les débutants.
Chris Betti
7
@eternalmatt, votre objection est tout à fait raisonnable, mais je pense que la valeur de onActivityResult () est qu'il est garanti d'exister sur n'importe quel fragment, donc n'importe quel fragment peut être utilisé comme parent. Si vous créez votre propre interface et que le fragment parent l'implémente, alors l'enfant ne peut être utilisé qu'avec les parents qui implémentent cette interface. Le couplage de l'enfant à cette interface pourrait revenir vous hanter si vous commencez à l'utiliser plus largement plus tard. L'utilisation de l'interface onActivityResult () "intégrée" ne nécessite aucun couplage supplémentaire, elle vous permet donc un peu plus de flexibilité.
Dalbergia
78

La solution TargetFragment ne semble pas être la meilleure option pour les fragments de boîte de dialogue, car elle peut se créer IllegalStateExceptionaprès la destruction et la recréation de l'application. Dans ce cas, vous FragmentManagerne trouvez pas le fragment cible et vous obtiendrez IllegalStateExceptionun message comme celui-ci:

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

Il semble que ce Fragment#setTargetFragment()n'est pas destiné à la communication entre un enfant et un parent Fragment, mais plutôt à la communication entre frères et sœurs-Fragments.

Une autre façon est donc de créer des fragments de dialogue comme celui-ci en utilisant le ChildFragmentManagerdu fragment parent, plutôt qu'en utilisant les activités FragmentManager:

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

Et en utilisant une interface, dans la onCreateméthode du, DialogFragmentvous 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 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

Oguz Ozcan
la source
2
Cela fonctionne également avec le onAttach (contexte de contexte) ajouté dans l'api 23.
Santa Teclado
1
CECI devrait être la réponse acceptée. La réponse actuellement acceptée est boguée et les fragments ne sont pas destinés à être utilisés comme ça.
NecipAllef
3
@AhmadFadli Le problème ici est d'obtenir le bon contexte (parent) pour la communication entre les fragments. Si vous utilisez un fragment de dialogue comme enfant de l'activité, il ne devrait pas y avoir de confusion. FragmentManager de Activity et getActivity () pour récupérer le rappel est suffisant.
Oguz Ozcan du
1
Cela devrait être la réponse acceptée. C'est clairement expliqué et détaillé, ce n'est pas seulement du code lancé aux gens.
Vince
1
@lukecross ParentFragment est le fragment qui crée le DialogFragment (celui qui appelle show ()) Mais il semble que childFragmentManager ne survit pas à la reconfiguration / aux rotations d'écran ...
iwat0qs
34

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

  1. Créez une interface comme DialogFragmentCallbackInterfaceavec une méthode comme callBackMethod(Object data). Ce que vous appelleriez pour transmettre des données.
  2. Vous pouvez maintenant implémenter une DialogFragmentCallbackInterfaceinterface dans votre fragment commeMyFragment implements DialogFragmentCallbackInterface
  3. Au moment de la DialogFragmentcréation, définissez votre fragment appelantMyFragment comme fragment cible qui a créé DialogFragmentutilisez myDialogFragment.setTargetFragment(this, 0)check setTargetFragment (Fragment fragment, int requestCode)

    MyDialogFragment dialogFrag = new MyDialogFragment();
    dialogFrag.setTargetFragment(this, 1); 
  4. Obtenez votre objet de fragment cible dans votre DialogFragmenten l'appelant getTargetFragment()et en le DialogFragmentCallbackInterfaceconvertissant en. Vous pouvez maintenant 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.

Vijay Vankhede
la source
4
Cela devrait être la meilleure réponse. Très bonne réponse.
Md. Sajedul Karim
Assurez-vous également d'utiliser le même gestionnaire de fragments pour les fragments source et de destination, sinon getTargetFragment ne fonctionnera pas. Donc, si vous utilisez childFragmentManager, cela ne fonctionnera pas car le fragment source n'est pas validé par le gestionnaire de fragments enfants. Il est préférable de considérer ces 2 fragments comme des fragments frères plutôt que comme des fragments parent / enfant.
Jeudi
Franchement, il est préférable d'utiliser uniquement le modèle de fragment cible lors de la communication entre 2 fragments frères. En n'ayant pas d'écouteur, vous évitez de divulguer accidentellement le fragment1 dans fragment2. Lorsque vous utilisez le fragment cible, n'utilisez pas d'écouteur / de rappel. Utilisez uniquement onActivityResult(request code, resultcode, intent)pour renvoyer le résultat à fragment1. À partir de fragment1 setTargetFragment()et à partir de fragment2, utilisez getTargetFragment(). Lorsque vous utilisez un fragment parent / enfant pour fragmenter ou une activité pour fragmenter, vous pouvez utiliser un écouteur ou un rappel car il n'y a aucun risque de fuite de parent dans le fragment enfant.
Jeudi
@Thupten quand vous dites "fuite", voulez-vous dire fuite de mémoire ou "détails d'implémentation de fuite"? Je ne pense pas que la réponse de Vijay fuira plus de mémoire que d'utiliser onActivityResulty pour la valeur de retour. Les deux modèles garderont la référence au fragment cible. Si vous voulez dire des détails d'implémentation qui fuient, alors je pense que son modèle est encore meilleur que onActivityResult. La méthode de rappel est explicite (si elle est nommée correctement). Si tout ce que vous récupérez est OK et ANNULÉ, le premier fragment doit interpréter ce que cela signifie.
tir38
34

Peut-être un peu tard, mais peut aider d'autres personnes avec la même question que moi.

Vous pouvez utiliser setTargetFragmentsur Dialogavant de montrer, et dans la boîte de dialogue, vous pouvez appeler getTargetFragmentpour obtenir la référence.

Rui
la source
Voici une réponse à une autre question mais elle s'applique également à votre question et est une solution propre: stackoverflow.com/questions/28620026/…
user2288580
IllegalStateException pour moi
luke cross
19

Le guide Communiquer avec d'autres fragments indique que les fragments doivent communiquer via l'activité associée .

Vous souhaiterez souvent qu'un fragment communique avec un autre, par exemple pour modifier le contenu en fonction d'un événement utilisateur. Toutes les communications de fragment à fragment se font via l'activité associée. Deux fragments ne doivent jamais communiquer directement.

Edward Brey
la source
1
qu'en est-il des fragments internes, c'est-à-dire comment un fragment dans un autre fragment devrait-il communiquer avec le fragment hôte
Ravi
@Ravi: Chaque fragment doit communiquer avec l'activité commune à tous les fragments en appelant getActivity () .
Edward Brey
1
@Chris: Si les fragments nécessitent une communication continue, définissez une interface pour chaque fragment approprié à implémenter. Le travail de l'activité se limite alors à fournir des fragments avec des pointeurs d'interface vers leurs fragments homologues. Après cela, les fragments peuvent communiquer "directement" en toute sécurité via les interfaces.
Edward Brey
3
Je pense qu'au fur et à mesure que les utilisations des fragments se sont développées, l'idée originale de ne pas utiliser la communication directe par fragments s'effondre. Par exemple, dans un tiroir de navigation, chaque fragment enfant immédiat de l'activité agit grosso modo comme une activité. Ainsi, avoir un fragment tel qu'un dialoguefragment communiquer à travers l'activité nuit à la lisibilité / flexibilité de l'OMI. En fait, il ne semble pas y avoir de moyen agréable d'encapsuler un fragment de dialogue pour lui permettre de fonctionner à la fois avec des activités et des fragments de manière réutilisable.
Sam
16
Je sais que c'est vieux, mais au cas où quelqu'un d'autre viendrait ici, j'ai l'impression que le cas évoqué dans ce document ne s'applique pas lorsqu'un fragment "possède" la logique qui est utilisée pour déterminer la création et la gestion du DialogFragment. C'est un peu bizarre de créer un tas de connexions entre le fragment et l'activité lorsque l'activité ne sait même pas pourquoi une boîte de dialogue est créée ou dans quelles conditions elle doit être rejetée. De plus, DialogFragment est super simple et n'existe que pour avertir l'utilisateur et éventuellement obtenir une réponse.
Chris du
12

Vous devez définir un interfacedans 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){

    }
    ...
}
James McCracken
la source
1
Je pense que vous avez parcouru les documents trop rapidement. Ces deux segments de code sont FragmentAet il suppose qu'une activité est un OnArticleSelectedListener, pas le Fragment qui l'a démarré.
eternalmatt
2
Je considérerais ce que vous essayez de faire de mauvaises pratiques. Les directives Android recommandent que toutes les communications de fragment à fragment aient lieu via l'activité (par developer.android.com/training/basics/fragments/… ). Si vous voulez vraiment que tout soit géré à l'intérieur, MyFragmentvous voudrez peut-être passer à un standardAlertDialog
James McCracken
1
Je pense que le souci de faire en sorte que les fragments se parlent directement est que dans certaines mises en page, tous les fragments ne peuvent pas être chargés et, comme ils le montrent dans l'exemple, il peut être nécessaire de basculer dans le fragment. Je ne pense pas que cette préoccupation soit valable lorsque l'on parle de lancer un fragment de dialogue à partir d'un fragment.
Je l'ai implémenté pour mes activités. Question: cette solution peut-elle être étendue pour qu'un fragment puisse instancier ce dialogue?
Bill Mote
1
C'est une bonne pratique d'un point de vue architectural et, en tant que telle, devrait être la réponse acceptée. L'utilisation de onActivityResult mène à l'architecture spaghetti
Bruno Carrier
4

La manière correcte de définir un écouteur sur un fragment est de 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 dans 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() {

            }
        });
    }
}
user1354603
la source
3

Selon la documentation officielle:

Fragment # setTargetFragment

Cible facultative pour ce fragment. Cela peut être utilisé, par exemple, si ce fragment est en cours de démarrage par un autre, et une fois terminé, il souhaite renvoyer un résultat au premier. La cible définie ici est conservée dans toutes les instances via FragmentManager # putFragment.

Fragment # getTargetFragment

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

Vous pouvez donc 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");
        }
    }
    ...
}
SUPERYAO
la source
pouvez-vous expliquer votre?
Yilmaz
Dans ce cas, nous devons passer la référence "MyFragment" à "MyialogFragment", et "Fragment" fournit la méthode pour le faire. J'ai ajouté la description du document officiel, cela devrait être plus clair que moi.
SUPERYAO
2

J'étais confronté à un problème similaire. La solution que j'ai trouvée était:

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

  2. Implémentez l'interface dans votre activité (pas fragmentée! 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 -> Activity puis Activity -> Fragment

Shailesh Mani Pandey
la source
1

J'obtiens le résultat vers Fragment DashboardLiveWall (fragment d'appel) de Fragment LiveWallFilterFragment (fragment de réception) Comme ceci ...

 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 revient à appeler 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 {
                    }
                }
            }
        }
    }
Zar E Ahmer
la source
1

Actualisé:

J'ai créé une bibliothèque basée sur mon code général qui génère ces castings pour vous en utilisant @CallbackFragmentet @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 fait une BaseCallbackFragmentannotation @FragmentCallback. Il s'étend actuellement Fragment, vous pouvez le changer DialogFragmentet fonctionnera. Il vérifie les implémentations dans l'ordre suivant: getTargetFragment ()> getParentFragment ()> context (activité).

Ensuite, il vous suffit de l'étendre et de déclarer vos interfaces dans votre fragment et de lui donner l'annotation, et le fragment de base fera le reste. L'annotation a également un paramètre qui mandatoryvous 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

Arst
la source
1

Les gars de Kotlin, nous y voilà!

Donc, le problème que nous avons est que nous avons créé une activité,, MainActivitysur cette activité, nous avons créé un fragment, FragmentAet maintenant nous voulons créer un fragment de dialogue en plus de l' FragmentAappeler FragmentB. Comment pouvons-nous obtenir les résultats de FragmentBdos à FragmentAsans passer MainActivity?

Remarque:

  1. FragmentAest un fragment enfant de MainActivity. Pour gérer les fragments créés dans FragmentAnous utiliserons childFragmentManagerqui fait cela!
  2. FragmentAest un fragment parent de FragmentB, pour accéder FragmentAde l'intérieur, FragmentBnous allons utiliser parenFragment.

Cela dit, à l'intérieur 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")
    }
}

Voici le fragment de dialogue 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")
    }
}

Voici l'interface qui relie les deux.

interface UpdateNameListener {
    fun onSave(name: String)
}

C'est tout.

Ssenyonjo
la source
1
J'ai suivi ce document: developer.android.com/guide/topics/ui/dialogs et cela n'a pas fonctionné. Merci beaucoup. J'espère que ce truc parentfragment fonctionne comme prévu à chaque fois :)
UmutTekin
1
n'oubliez pas de mettre l'auditeur à null dans onDetach :)
BekaBot
@BekaBot Merci pour le commentaire. J'ai fait quelques recherches et il semble que ce n'est pas nécessaire de fermer les auditeurs. stackoverflow.com/a/37031951/10030693
Ssenyonjo
0

J'ai résolu cela de manière élégante avec RxAndroid. Recevoir un observateur dans le constructeur du DialogFragment et souscrire à observable et pousser la valeur lorsque le rappel est appelé. Ensuite, dans votre Fragment, créez une classe interne de l'Observer, créez une instance et passez-la dans le 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

Gerardo Suarez
la source
1
Le plus drôle à propos d'Android est qu'il y a quelque chose qui s'appelle le cycle de vie. Le fragment de base ou le fragment de boîte de dialogue doit pouvoir conserver l'état (et leur connexion) sur les événements du cycle de vie. Les rappels ou les observateurs ne peuvent pas être sérialisés et ont donc le même problème ici.
GDanger