Recevoir le résultat de DialogFragment

232

J'utilise DialogFragments pour un certain nombre de choses: choisir un élément dans la liste, saisir du texte.

Quelle est la meilleure façon de renvoyer une valeur (c'est-à-dire une chaîne ou un élément d'une liste) à l'activité / au fragment appelant?

Actuellement, je fais implémenter l'activité d'appel DismissListeneret donne au DialogFragment une référence à l'activité. La boîte de dialogue appelle ensuite la OnDimissméthode dans l'activité et l'activité récupère le résultat de l'objet DialogFragment. Très désordonné et cela ne fonctionne pas sur le changement de configuration (changement d'orientation) car DialogFragment perd la référence à l'activité.

Merci pour toute aide.

James Cross
la source
10
DialogFragments ne sont encore que des fragments. Votre approche est en fait la manière recommandée pour que les fragments utilisent pour parler de l'activité principale. developer.android.com/guide/topics/fundamentals/…
codinguser
1
Merci pour ça. J'étais très proche (comme vous l'avez dit). Le bit que ce document lié m'a aidé à utiliser onAttach () et à diffuser l'activité à un auditeur.
James Cross
2
@codinguser, @Styx - "donner au DialogFragment une référence à l'activité" - ce détail est un peu risqué, car le Activityet le DialogFragmentpeuvent être recréés. L'utilisation du Activitypassé à onAttach(Activity activity)est la manière appropriée et recommandée.
sstn

Réponses:

246

Utilisez à myDialogFragment.setTargetFragment(this, MY_REQUEST_CODE)partir de l'endroit où vous affichez la boîte de dialogue, puis lorsque votre boîte de dialogue est terminée, vous pouvez l'appeler getTargetFragment().onActivityResult(getTargetRequestCode(), ...)et l'implémenter onActivityResult()dans le fragment contenant.

Cela semble être un abus onActivityResult(), d'autant plus qu'il n'implique aucune activité. Mais je l'ai vu recommandé par les responsables officiels de Google, et peut-être même dans les démos de l'API. Je pense que c'est ce qui a g/setTargetFragment()été ajouté.

Timmmm
la source
2
setTargetFragment mentionne que le code de demande est à utiliser dans onActivityResult, donc je suppose que c'est correct d'utiliser cette approche.
Giorgi
87
Et si la cible est une activité?
Fernando Gallego
9
Si la cible est une activité, je déclarerais l'interface avec une méthode telle que "void onActivityResult2 (int requestCode, int resultCode, Intent data)" et l'implémenterais par une activité. Dans DialogFragment, il suffit d'obtenir getActivity et de rechercher cette interface et de l'appeler de manière appropriée.
Ruslan Yanchyshyn
4
Ce n'est pas une bonne solution. Il ne fonctionnera pas après l'enregistrement et la restauration de l'état du fragment de dialogue. LocalBroadcastManager est la meilleure solution dans ce cas.
Nik
4
@Nik Ce n'est tout simplement pas vrai. C'est la meilleure solution. Il n'y a aucun problème lors de l'enregistrement et de la restauration de l'état. Si vous avez déjà eu un problème, vous avez utilisé le mauvais gestionnaire de fragments. Le fragment / l'appelant cible doit utiliser getChildFragmentManager () pour afficher la boîte de dialogue.
L'incroyable
140

Comme vous pouvez le voir ici, il existe un moyen très simple de le faire.

Dans votre DialogFragmentajouter un écouteur d'interface comme:

public interface EditNameDialogListener {
    void onFinishEditDialog(String inputText);
}

Ensuite, ajoutez une référence à cet écouteur:

private EditNameDialogListener listener;

Cela sera utilisé pour "activer" la ou les méthodes d'écoute, et également pour vérifier si l'activité / le fragment parent implémente cette interface (voir ci-dessous).

Dans le Activity/ FragmentActivity/Fragment qui "appelé", DialogFragmentimplémentez simplement cette interface.

Dans votre DialogFragmenttout, vous devez ajouter au point où vous souhaitez supprimer le DialogFragmentet renvoyer le résultat est le suivant:

listener.onFinishEditDialog(mEditText.getText().toString());
this.dismiss();

mEditText.getText().toString()est ce qui sera renvoyé à l'appel Activity.

Notez que si vous souhaitez renvoyer autre chose, changez simplement les arguments pris par l'auditeur.

Enfin, vous devez vérifier si l'interface a été réellement implémentée par l'activité / le fragment parent:

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    // Verify that the host activity implements the callback interface
    try {
        // Instantiate the EditNameDialogListener so we can send events to the host
        listener = (EditNameDialogListener) context;
    } catch (ClassCastException e) {
        // The activity doesn't implement the interface, throw exception
        throw new ClassCastException(context.toString()
                + " must implement EditNameDialogListener");
    }
}

Cette technique est très flexible et permet de rappeler le résultat même si vous ne souhaitez pas fermer la boîte de dialogue pour l'instant.

Assaf Gamliel
la source
13
Cela fonctionne très bien avec Activity'et FragmentActivity' mais si l'appelant est-il un Fragment?
Brais Gabin
1
Je ne suis pas sûr de bien vous comprendre. Mais cela fonctionnera de la même manière si l'appelant est un Fragment.
Assaf Gamliel
2
Si l'appelant était un Fragmentalors vous pouvez faire quelques choses: 1. Passez le fragment comme référence (Ce n'est peut-être pas une bonne idée car vous pourriez provoquer des fuites de mémoire). 2. Utilisez le FragmentManageret appelez findFragmentByIdou findFragmentByTagil obtiendra les fragments qui existent dans votre activité. J'espère que cela a aidé. Passez une bonne journée!
Assaf Gamliel
5
Le problème avec cette approche est que les fragments ne sont pas très bons pour retenir les objets car ils sont destinés à être recréés, par exemple essayez de changer l'orientation, le système d'exploitation recréera le fragment mais l'instance de l'écouteur ne sera plus disponible
Necronet
5
@LOG_TAG regardez la réponse de @ Timmmm. setTargetFragment()et getTargetFragment()sont magiques.
Brais Gabin
48

Il existe un moyen beaucoup plus simple de recevoir un résultat d'un DialogFragment.

Tout d'abord, dans votre activité, fragment ou activité de fragment, vous devez ajouter les informations suivantes:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    // Stuff to do, dependent on requestCode and resultCode
    if(requestCode == 1) { // 1 is an arbitrary number, can be any int
         // This is the return result of your DialogFragment
         if(resultCode == 1) { // 1 is an arbitrary number, can be any int
              // Now do what you need to do after the dialog dismisses.
         }
     }
}

C'est requestCodefondamentalement votre étiquette int pour le DialogFragment que vous avez appelé, je vais montrer comment cela fonctionne dans une seconde. Le resultCode est le code que vous renvoyez du DialogFragment indiquant à votre Activity, Fragment ou FragmentActivity en attente ce qui s'est passé.

Le morceau de code suivant à entrer est l'appel au DialogFragment. Un exemple est ici:

DialogFragment dialogFrag = new MyDialogFragment();
// This is the requestCode that you are sending.
dialogFrag.setTargetFragment(this, 1);     
// This is the tag, "dialog" being sent.
dialogFrag.show(getFragmentManager(), "dialog");

Avec ces trois lignes, vous déclarez votre DialogFragment, définissez un requestCode (qui appellera onActivityResult (...) une fois la boîte de dialogue fermée et vous affichez ensuite la boîte de dialogue. C'est aussi simple que cela.

Maintenant, dans votre DialogFragment, vous devez simplement ajouter une ligne juste avant le dismiss()afin de renvoyer un resultCode au onActivityResult ().

getTargetFragment().onActivityResult(getTargetRequestCode(), resultCode, getActivity().getIntent());
dismiss();

C'est tout. Remarque, le resultCode est défini comme int resultCodecelui que j'ai défini resultCode = 1;dans ce cas.

Voilà, vous pouvez maintenant renvoyer le résultat de votre DialogFragment à votre activité d'appel, fragment ou activité de fragment.

De plus, il semble que ces informations aient été publiées précédemment, mais aucun exemple suffisant n'a été donné, j'ai donc pensé fournir plus de détails.

EDIT 06.24.2016 Je m'excuse pour le code trompeur ci-dessus. Mais vous ne pouvez certainement pas recevoir le résultat de l'activité en tant que ligne:

dialogFrag.setTargetFragment(this, 1);

fixe un objectif Fragmentet non Activity. Donc, pour ce faire, vous devez utiliser implémenter un InterfaceCommunicator.

Dans votre DialogFragmentensemble une variable globale

public InterfaceCommunicator interfaceCommunicator;

Créer une fonction publique pour la gérer

public interface InterfaceCommunicator {
    void sendRequestCode(int code);
}

Ensuite , lorsque vous êtes prêt à envoyer le dos de code à Activityquand l' DialogFragmentest fait en cours d' exécution, vous ajoutez simplement la ligne avant dismiss();votre DialogFragment:

interfaceCommunicator.sendRequestCode(1); // the parameter is any int code you choose.

Dans votre activité, vous devez maintenant faire deux choses, la première consiste à supprimer cette seule ligne de code qui n'est plus applicable:

dialogFrag.setTargetFragment(this, 1);  

Ensuite, implémentez l'interface et vous avez terminé. Vous pouvez le faire en ajoutant la ligne suivante à la implementsclause tout en haut de votre classe:

public class MyClass Activity implements MyDialogFragment.InterfaceCommunicator

Et puis @Overridela fonction dans l'activité,

@Override
public void sendRequestCode(int code) {
    // your code here
}

Vous utilisez cette méthode d'interface comme vous le feriez pour la onActivityResult()méthode. Sauf que la méthode d'interface est pour DialogFragmentset l'autre est pour Fragments.

Brandon
la source
4
Cette approche ne fonctionnera pas si la cible est Activity car vous ne pouvez pas appeler son onActivityResult (à partir de votre DialogFragment) en raison du niveau d'accès protégé.
Ruslan Yanchyshyn
2
Ce n'est tout simplement pas vrai. J'utilise ce code exact dans mes projets. C'est de là que je l'ai tiré et ça marche très bien. N'oubliez pas que si vous rencontrez ce problème de niveau d'accès protégé, vous pouvez modifier votre niveau d'accès pour n'importe quelle méthode et classe de protégé à privé ou public si nécessaire.
Brandon
6
Bonjour, vous dites que vous pouvez appeler à dialogFrag.setTargetFragment(this, 1)partir d'une activité, mais cette méthode reçoit un fragment comme premier argument, donc cela n'a pas pu être lancé. Ai-je raison ?
MondKin
1
Je posterai quelques réponses pour vous tous dans quelques-uns pour expliquer les activités.
Brandon
1
@Swift @lcompare, vous devrez probablement remplacer onAttach (Context context) dans votre DialogFragment. Comme ça:@Override public void onAttach(Context context) { super.onAttach(context); yourInterface = (YourInterface) context; }
lidkxx
20

Il est peut-être trop tard pour répondre, mais voici ce que j'ai fait pour obtenir des résultats de la DialogFragment. très similaire à la réponse de @ brandon. Ici, j'appelle à DialogFragmentpartir d'un fragment, placez simplement ce code là où vous appelez votre boîte de dialogue.

FragmentManager fragmentManager = getFragmentManager();
            categoryDialog.setTargetFragment(this,1);
            categoryDialog.show(fragmentManager, "dialog");

categoryDialogest mon DialogFragmentque je veux appeler et après cela dans votre implémentation de dialogfragmentplacer ce code où vous définissez vos données dans l'intention. La valeur de resultCodeest 1, vous pouvez la définir ou utiliser Défini par le système.

            Intent intent = new Intent();
            intent.putExtra("listdata", stringData);
            getTargetFragment().onActivityResult(getTargetRequestCode(), resultCode, intent);
            getDialog().dismiss();

il est maintenant temps de revenir au fragment appelant et d'implémenter cette méthode. vérifiez la validité des données ou la réussite des résultats si vous le souhaitez avec resultCodeet requestCodedans la condition if.

 @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);        
        //do what ever you want here, and get the result from intent like below
        String myData = data.getStringExtra("listdata");
Toast.makeText(getActivity(),data.getStringExtra("listdata"),Toast.LENGTH_SHORT).show();
    }
vikas kumar
la source
10

Approche différente, pour permettre à un Fragment de communiquer jusqu'à son Activité :

1) Définissez une interface publique dans le fragment et créez une variable pour celle-ci

public OnFragmentInteractionListener mCallback;

public interface OnFragmentInteractionListener {
    void onFragmentInteraction(int id);
}

2) Cast l'activité sur la variable mCallback dans le fragment

try {
    mCallback = (OnFragmentInteractionListener) getActivity();
} catch (Exception e) {
    Log.d(TAG, e.getMessage());
}

3) Implémentez l'auditeur dans votre activité

public class MainActivity extends AppCompatActivity implements DFragment.OnFragmentInteractionListener  {
     //your code here
}

4) Remplacer l'interaction OnFragment dans l'activité

@Override
public void onFragmentInteraction(int id) {
    Log.d(TAG, "received from fragment: " + id);
}

Plus d'informations à ce sujet: https://developer.android.com/training/basics/fragments/communicating.html

comparer
la source
Merci de l'avoir si bien résumé. Juste une note pour les autres, le tutoriel Android Devs suggère de remplacer public void onAttachle fragment et de faire le casting d'activité
Big_Chair
8

Un moyen simple que j'ai trouvé était le suivant: implémentez ceci est votre dialogFragment,

  CallingActivity callingActivity = (CallingActivity) getActivity();
  callingActivity.onUserSelectValue("insert selected value here");
  dismiss();

Et puis, dans l'activité qui a appelé le fragment de dialogue, créez la fonction appropriée en tant que telle:

 public void onUserSelectValue(String selectedValue) {

        // TODO add your implementation.
      Toast.makeText(getBaseContext(), ""+ selectedValue, Toast.LENGTH_LONG).show();
    }

Le Toast est de montrer que cela fonctionne. A travaillé pour moi.

ZeWolfe15
la source
Je ne sais pas si c'est une bonne façon de le faire, mais cela fonctionne certainement :)
soshial
Meilleure utilisation Interfaceplutôt que couplage dur avec des classes concrètes.
waqaslam
6

Je suis très surpris de voir que personne ne l'a suggéré d' utiliser des émissions locales pour DialogFragmentla Activitycommunication! Je trouve que c'est tellement plus simple et plus propre que les autres suggestions. Essentiellement, vous vous inscrivez pour Activityécouter les émissions et vous envoyez les émissions locales à partir de vos DialogFragmentinstances. Facile. Pour un guide étape par étape sur la façon de tout configurer, voir ici .

Adil Hussain
la source
2
J'aime cette solution, est-ce considéré comme une bonne ou meilleure pratique sous Android?
Benjamin Scharbau
1
J'ai vraiment aimé le tutoriel, merci de l'avoir posté. Je veux ajouter que, selon ce que vous essayez d'accomplir, l'une ou l'autre méthode peut être plus utile que l'autre. Je suggérerais l'itinéraire de diffusion local si vous avez plusieurs entrées / résultats renvoyés à l'activité à partir de la boîte de dialogue. Je recommanderais d'utiliser la route onActivityResult si votre sortie est très basique / simple. Donc, pour répondre à la question des meilleures pratiques, cela dépend de ce que vous essayez d'accomplir!
Brandon
1
@AdilHussain Vous avez raison. J'ai fait l'hypothèse que les gens utilisaient des fragments dans leurs activités. L'option setTargetFragment est idéale si vous communiquez avec un fragment et un DialogFragment. Mais vous devez utiliser la méthode Broadcast lorsqu'il s'agit d'une activité appelant DialogFragment.
Brandon
3
Pour l'amour de Foo, n'utilisez pas de diffusions !! Il ouvre votre application à une multitude de problèmes de sécurité. Je trouve également que la pire application Android sur laquelle je dois travailler est la diffusion d'abus. Pouvez-vous penser à une meilleure façon de rendre le code complètement inutilisable? Maintenant, je dois déraciner les récepteurs de diffusion, au lieu d'une ligne de code CLEAR? Pour être clair, il existe des utilisations pour les émissions, mais pas dans ce contexte! JAMAIS dans ce contexte! C'est juste bâclé. Local ou non. Les rappels sont tout ce dont vous avez besoin.
StarWind0
1
En plus du Guava EventBus, une autre option est le GreenRobot EventBus . Je n'ai pas utilisé le Guava EventBus mais j'ai utilisé le GreenRobot EventBus et j'en ai eu une bonne expérience. Agréable et simple à utiliser. Pour un petit exemple de la façon d'architecturer une application Android pour utiliser le GreenRobot EventBus, voir ici .
Adil Hussain
3

Ou partagez ViewModel comme montré ici:

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData<Item> getSelected() {
        return selected;
    }
}


public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // Update the UI.
        });
    }
}

https://developer.android.com/topic/libraries/architecture/viewmodel#sharing_data_between_fragments

Malachiasz
la source
2

Dans mon cas, j'avais besoin de passer des arguments à un targetFragment. Mais j'ai eu l'exception "Fragment déjà actif". J'ai donc déclaré une interface dans mon DialogFragment que parentFragment a implémenté. Lorsque parentFragment a démarré un DialogFragment, il s'est défini comme TargetFragment. Puis dans DialogFragment j'ai appelé

 ((Interface)getTargetFragment()).onSomething(selectedListPosition);
Lanitka
la source
2

À Kotlin

    // My DialogFragment
class FiltroDialogFragment : DialogFragment(), View.OnClickListener {
    
    var listener: InterfaceCommunicator? = null

    override fun onAttach(context: Context?) {
        super.onAttach(context)
        listener = context as InterfaceCommunicator
    }

    interface InterfaceCommunicator {
        fun sendRequest(value: String)
    }   

    override fun onClick(v: View) {
        when (v.id) {
            R.id.buttonOk -> {    
        //You can change value             
                listener?.sendRequest('send data')
                dismiss()
            }
            
        }
    }
}

// Mon activité

class MyActivity: AppCompatActivity(),FiltroDialogFragment.InterfaceCommunicator {

    override fun sendRequest(value: String) {
    // :)
    Toast.makeText(this, value, Toast.LENGTH_LONG).show()
    }
}
 

J'espère que cela sert, si vous pouvez l'améliorer, veuillez le modifier. Mon anglais n'est pas très bon

Irvin Joao
la source
Dans mon cas, la boîte de dialogue est créée à partir d'un fragment et non d'une activité, cette solution ne fonctionne donc pas. Mais j'aime le smiley que tu mets :)
Ssenyonjo
0

si vous souhaitez envoyer des arguments et recevoir le résultat du deuxième fragment, vous pouvez utiliser Fragment.setArguments pour accomplir cette tâche

static class FirstFragment extends Fragment {
    final Handler mUIHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case 101: // receive the result from SecondFragment
                Object result = msg.obj;
                // do something according to the result
                break;
            }
        };
    };

    void onStartSecondFragments() {
        Message msg = Message.obtain(mUIHandler, 101, 102, 103, new Object()); // replace Object with a Parcelable if you want to across Save/Restore
                                                                               // instance
        putParcelable(new SecondFragment(), msg).show(getFragmentManager().beginTransaction(), null);
    }
}

static class SecondFragment extends DialogFragment {
    Message mMsg; // arguments from the caller/FirstFragment

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onViewCreated(view, savedInstanceState);
        mMsg = getParcelable(this);
    }

    void onClickOK() {
        mMsg.obj = new Object(); // send the result to the caller/FirstFragment
        mMsg.sendToTarget();
    }
}

static <T extends Fragment> T putParcelable(T f, Parcelable arg) {
    if (f.getArguments() == null) {
        f.setArguments(new Bundle());
    }
    f.getArguments().putParcelable("extra_args", arg);
    return f;
}
static <T extends Parcelable> T getParcelable(Fragment f) {
    return f.getArguments().getParcelable("extra_args");
}
Yessy
la source
-3

Juste pour l'avoir comme l'une des options (puisque personne ne l'a encore mentionné) - vous pouvez utiliser un bus d'événements comme Otto. Donc, dans la boîte de dialogue, vous faites:

bus.post(new AnswerAvailableEvent(42));

Et demandez à votre correspondant (Activité ou Fragment) de s'y abonner:

@Subscribe public void answerAvailable(AnswerAvailableEvent event) {
   // TODO: React to the event somehow!
}
Dennis K
la source