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.