Ricevi il risultato da DialogFragment


232

Sto usando DialogFragments per una serie di cose: scegliere l'elemento dalla lista, inserire il testo.

Qual è il modo migliore per restituire un valore (ovvero una stringa o un elemento da un elenco) all'attività / frammento chiamante?

Attualmente sto implementando l'attività di chiamata DismissListenere dando a DialogFragment un riferimento all'attività. La finestra di dialogo chiama quindi il OnDimissmetodo nell'attività e l'attività acquisisce il risultato dall'oggetto DialogFragment. Molto disordinato e non funziona sul cambio di configurazione (cambio di orientamento) in quanto DialogFragment perde il riferimento all'attività.

Grazie per qualsiasi aiuto.


10
DialogFragments sono ancora solo frammenti. Il tuo approccio è in realtà il modo raccomandato per usare i frammenti per rispondere all'attività principale. developer.android.com/guide/topics/fundamentals/…
codinguser

1
Grazie per quello Ero molto vicino (come hai detto). Il bit con cui quel documento collegato mi ha aiutato è stato usare onAttach () e trasmettere l'attività a un ascoltatore.
James Cross,

2
@codinguser, @Styx - "dando a DialogFragment un riferimento all'attività" - questo dettaglio è un po 'rischioso, poiché sia ​​il Activityche il DialogFragmentpotrebbero essere ricreati. L'utilizzo del Activitypassaggio a onAttach(Activity activity)è il modo corretto e consigliato.
sstn

Risposte:


246

Utilizzare myDialogFragment.setTargetFragment(this, MY_REQUEST_CODE)dal punto in cui viene mostrata la finestra di dialogo, quindi al termine della finestra di dialogo, da essa è possibile chiamare getTargetFragment().onActivityResult(getTargetRequestCode(), ...)e implementare onActivityResult()nel frammento contenente.

Sembra un abuso di onActivityResult(), soprattutto perché non coinvolge attività. Ma l'ho visto consigliato da google ufficiali e forse anche nelle demo di API. Penso che sia quello per cui g/setTargetFragment()sono stati aggiunti.


2
setTargetFragment menziona che il requestcode deve essere usato in onActivityResult, quindi credo che sia giusto usare questo approccio.
Giorgi,

87
Cosa succede se il target è un'attività?
Fernando Gallego,

9
Se il target è attività, dichiarerei l'interfaccia con un metodo come "void onActivityResult2 (int requestCode, int resultCode, Intent data)" e la implementerei con un'attività. In DialogFragment basta ottenereActivity e verificare questa interfaccia e chiamarla in modo appropriato.
Ruslan Yanchyshyn,

4
Non è una buona soluzione. Non funzionerà dopo il salvataggio e il ripristino dello stato del frammento della finestra di dialogo. LocalBroadcastManager è la soluzione migliore in questo caso.
Nik

4
@Nik Questo non è vero. È la soluzione migliore. Non ci sono problemi durante il salvataggio e il ripristino dello stato. Se hai mai avuto un problema hai usato il gestore frammento sbagliato. Il frammento / chiamante di destinazione deve usare getChildFragmentManager () per mostrare la finestra di dialogo.
L'incredibile gennaio

140

Come puoi vedere qui c'è un modo molto semplice per farlo.

Nel tuo DialogFragmentaggiungi un listener di interfaccia come:

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

Quindi, aggiungi un riferimento a quel listener:

private EditNameDialogListener listener;

Verrà utilizzato per "attivare" i metodi di ascolto e per verificare se l'attività / il frammento parent implementa questa interfaccia (vedere di seguito).

Nel Activity/ FragmentActivity/ Fragmentche "chiamato" ilDialogFragment semplicemente implementare questa interfaccia.

In DialogFragmenttutto ciò che devi aggiungere nel punto in cui desideri eliminare il DialogFragmente restituire il risultato è questo:

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

Dove si mEditText.getText().toString()trova ciò che verrà restituito alla chiamata Activity.

Nota che se vuoi restituire qualcos'altro, cambia semplicemente gli argomenti che l'ascoltatore accetta.

Infine, è necessario verificare se l'interfaccia è stata effettivamente implementata dall'attività / frammento padre:

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

Questa tecnica è molto flessibile e consente di richiamare con il risultato anche se non si desidera ancora chiudere la finestra di dialogo.


13
Funziona alla grande con Activity'e FragmentActivity' ma se il chiamante è un Fragment?
Brais Gabin,

1
Non sono sicuro di capirti fino in fondo. Funzionerà allo stesso modo se il chiamante è a Fragment.
Assaf Gamliel,

2
Se il chiamante era a, Fragmentallora puoi fare alcune cose: 1. Passa il frammento come riferimento (Potrebbe non essere una buona idea perché potresti causare perdite di memoria). 2. Utilizzare il tasto FragmentManagere chiama findFragmentByIdo findFragmentByTagotterrà i frammenti esistenti nella propria attività. Spero abbia aiutato. Vi auguro una buona giornata!
Assaf Gamliel,

5
Il problema con questo approccio è che i frammenti non sono molto bravi a trattenere l'oggetto poiché sono pensati per essere ricreati, ad esempio provano a cambiare l'orientamento, il sistema operativo ricrea il frammento ma l'istanza dell'ascoltatore non sarà più disponibile
Necronet

5
@LOG_TAG guarda la risposta di @ Timmmm. setTargetFragment()e getTargetFragment()sono magici.
Brais Gabin,

48

C'è un modo molto più semplice per ricevere un risultato da un DialogFragment.

Innanzitutto, in Attività, Frammento o FrammentoAttività è necessario aggiungere le seguenti informazioni:

@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.
         }
     }
}

L' requestCodeè fondamentalmente l'etichetta int per il DialogFragment hai chiamato, ti faccio vedere come funziona in un secondo. ResultCode è il codice che rispedisci da DialogFragment indicando cosa è successo all'attività di attesa, al frammento o all'attività del frammento in corso.

Il prossimo pezzo di codice da inserire è la chiamata a DialogFragment. Un esempio è qui:

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

Con queste tre righe stai dichiarando il tuo DialogFragment, impostando un requestCode (che chiamerà onActivityResult (...) una volta che il dialogo è stato chiuso, e poi stai mostrando la finestra di dialogo. È così semplice.

Ora, nel tuo DialogFragment devi solo aggiungere una riga direttamente prima di in dismiss()modo da inviare un resultCode a onActivityResult ().

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

Questo è tutto. Nota, il resultCode è definito come quello int resultCodeche ho impostato resultCode = 1;in questo caso.

Ecco fatto, ora puoi inviare il risultato del tuo DialogFragment all'attività chiamante, al frammento o all'attività del frammento.

Inoltre, sembra che queste informazioni siano state pubblicate in precedenza, ma non è stato fornito un esempio sufficiente, quindi ho pensato di fornire maggiori dettagli.

MODIFICA 24/06/2016 Mi scuso per il codice fuorviante di cui sopra. Ma sicuramente non puoi ricevere il risultato dall'attività visto come la linea:

dialogFrag.setTargetFragment(this, 1);

imposta un obiettivo Fragmente non Activity. Quindi per fare questo è necessario usare implement an InterfaceCommunicator.

Nel tuo DialogFragmentset una variabile globale

public InterfaceCommunicator interfaceCommunicator;

Crea una funzione pubblica per gestirla

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

Poi, quando si è pronti per inviare il codice di nuovo alla Activityquando l' DialogFragmentè fatto in esecuzione, è sufficiente aggiungere la linea prima di dismiss();tuo DialogFragment:

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

Nella tua attività ora devi fare due cose, la prima è rimuovere quella riga di codice che non è più applicabile:

dialogFrag.setTargetFragment(this, 1);  

Quindi implementa l'interfaccia e il gioco è fatto. Puoi farlo aggiungendo la seguente riga alla implementsclausola in cima alla tua classe:

public class MyClass Activity implements MyDialogFragment.InterfaceCommunicator

E poi @Overridela funzione nell'attività,

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

Si utilizza questo metodo di interfaccia proprio come si farebbe con il onActivityResult()metodo. Tranne il metodo di interfaccia è per DialogFragmentse l'altro è per Fragments.


4
Questo approccio non funzionerà se il target è Activity perché non è possibile chiamarlo onActivityResult (dal tuo DialogFragment) a causa del livello di accesso protetto.
Ruslan Yanchyshyn,

2
Questo semplicemente non è vero. Uso questo codice esatto nei miei progetti. È da lì che l'ho estratto e funziona perfettamente. Ricorda che se stai riscontrando questo problema relativo al livello di accesso protetto, puoi modificare il tuo livello di accesso per qualsiasi metodo e classe da protetto a privato o pubblico, se necessario.
Brandon,

6
Ciao, dici che puoi chiamare dialogFrag.setTargetFragment(this, 1)da un'attività, ma questo metodo riceve un frammento come primo argomento, quindi non è stato possibile lanciarlo. Ho ragione ?
MondKin,

1
Pubblicherò alcune risposte per tutti voi in pochi per spiegare le attività.
Brandon,

1
@Swift @lcompare probabilmente dovrai sovrascrivere onAttach (contesto di contesto) nel tuo DialogFragment. In questo modo:@Override public void onAttach(Context context) { super.onAttach(context); yourInterface = (YourInterface) context; }
lidkxx

20

Beh, è ​​troppo tardi per rispondere, ma ecco cosa ho fatto per ottenere risultati dal DialogFragment. molto simile alla risposta di @ brandon. Qui sto chiamando DialogFragmentda un frammento, basta inserire questo codice nel punto in cui stai chiamando la tua finestra di dialogo.

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

dov'è il categoryDialogmio DialogFragmentche voglio chiamare e dopo questo nella tua implementazione di dialogfragmentinserire questo codice in cui stai impostando i tuoi dati in modo mirato. Il valore di resultCodeè 1 è possibile impostarlo o utilizzare Definito dal sistema.

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

ora è il momento di tornare al frammento chiamante e implementare questo metodo. verificare la validità dei dati o il risultato positivo se si desidera con resultCodee requestCodein if condizione.

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

10

Approccio diverso, per consentire a un frammento di comunicare fino alla sua attività :

1) Definisci un'interfaccia pubblica nel frammento e creane una variabile

public OnFragmentInteractionListener mCallback;

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

2) Trasmetti l'attività alla variabile mCallback nel frammento

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

3) Implementa l'ascoltatore nella tua attività

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

4) Sovrascrivi OnFragmentInteraction nell'attività

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

Maggiori informazioni su di esso: https://developer.android.com/training/basics/fragments/communicating.html


Grazie per averlo riassunto così bene. Solo una nota per gli altri, il tutorial di Android Devs suggerisce di scavalcare public void onAttachil frammento e fare l'attività di casting lì
Big_Chair

8

Un modo semplice che ho trovato è stato il seguente: Implementa questo è il tuo dialogFragment,

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

E quindi nell'attività che ha chiamato il frammento della finestra di dialogo creare la funzione appropriata come tale:

 public void onUserSelectValue(String selectedValue) {

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

Il Toast è per dimostrare che funziona. Ha funzionato per me.


Non sono sicuro che sia il modo giusto di farlo, ma sicuramente funziona :)
soshial

Uso migliore Interfacepiuttosto che accoppiamento duro con classi concrete.
Waqaslam,

6

Sono molto sorpreso di vedere che nessuno ha suggerito di usare le trasmissioni locali per DialogFragmentper Activityla comunicazione! Trovo che sia molto più semplice e più pulito di altri suggerimenti. In sostanza, ti registri Activityaffinché ascolti le trasmissioni e invii le trasmissioni locali dalle tue DialogFragmentistanze. Semplice. Per una guida dettagliata su come impostare tutto, vedere qui .


2
Mi piace quella soluzione, è considerata una buona o migliore pratica in Android?
Benjamin Scharbau,

1
Mi è piaciuto molto il tutorial, grazie per averlo pubblicato. Voglio aggiungere che a seconda di ciò che stai cercando di realizzare uno dei due metodi può essere più utile dell'altro. Vorrei suggerire il percorso di trasmissione locale se si hanno più input / risultati inviati all'attività dalla finestra di dialogo. Consiglio di utilizzare il percorso onActivityResult se l'output è molto semplice / di base. Quindi, per rispondere alla domanda sulle migliori pratiche, dipende da cosa stai cercando di realizzare!
Brandon,

1
@AdilHussain Hai ragione. Ho ipotizzato che le persone stessero usando i frammenti nelle loro attività. L'opzione setTargetFragment è ottima se stai comunicando con un frammento e un dialogo. Ma devi usare il metodo Broadcast quando è un'attività che chiama DialogFragment.
Brandon,

3
Per amore di Foo, non usare le trasmissioni !! Apre l'applicazione a una serie di problemi di sicurezza. Inoltre trovo che la peggior applicazione Android che devo lavorare su trasmissioni di abuso. Riesci a pensare a un modo migliore per rendere il codice completamente inutilizzabile? Ora devo sradicare i ricevitori di trasmissione, invece di una riga di codice CLEAR? Per essere chiari ci sono usi per le trasmissioni, ma non in questo contesto! MAI in questo contesto! È solo sciatto. Locale o no. I callback sono tutto ciò che serve.
StarWind0,

1
Oltre a Guava EventBus, un'altra opzione è GreenBobot EventBus . Non ho usato Guava EventBus ma ho usato GreenBobot EventBus e ne ho avuto una buona esperienza. Bello e semplice da usare. Per un piccolo esempio di come architettare un'applicazione Android per utilizzare il GreenBobot EventBus, vedere qui .
Adil Hussain,

3

O condividi ViewModel come mostrato qui:

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


2

Nel mio caso avevo bisogno di passare argomenti a targetFragment. Ma ho ricevuto l'eccezione "Frammento già attivo". Così ho dichiarato un'interfaccia nel mio DialogFragment implementata da parentFragment. Quando parentFragment ha avviato DialogFragment, si è impostato come TargetFragment. Quindi in DialogFragment ho chiamato

 ((Interface)getTargetFragment()).onSomething(selectedListPosition);

2

A 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()
            }
            
        }
    }
}

// La mia attività

class MyActivity: AppCompatActivity(),FiltroDialogFragment.InterfaceCommunicator {

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

Spero che serva, se puoi migliorare per favore modificalo. Il mio inglese non è molto buono


Nel mio caso la finestra di dialogo viene creata da un frammento non da un'attività, quindi questa soluzione non funziona. Ma mi piace la
faccina che

0

se si desidera inviare argomenti e ricevere il risultato dal secondo frammento, è possibile utilizzare Fragment.setArguments per eseguire questa attività

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

-3

Solo per averlo come una delle opzioni (dal momento che nessuno lo ha ancora menzionato) - potresti usare un bus per eventi come Otto. Quindi nella finestra di dialogo fai:

bus.post(new AnswerAvailableEvent(42));

E abbonati al chiamante (Attività o Frammento):

@Subscribe public void answerAvailable(AnswerAvailableEvent event) {
   // TODO: React to the event somehow!
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.