Ergebnis von DialogFragment erhalten


232

Ich verwende DialogFragments für eine Reihe von Dingen: Auswahl eines Elements aus der Liste, Eingabe von Text.

Was ist der beste Weg, um einen Wert (dh eine Zeichenfolge oder ein Element aus einer Liste) an die aufrufende Aktivität / das aufrufende Fragment zurückzugeben?

Derzeit DismissListenerlasse ich die aufrufende Aktivität implementieren und gebe dem DialogFragment einen Verweis auf die Aktivität. Der Dialog ruft dann die OnDimissMethode in der Aktivität auf und die Aktivität erfasst das Ergebnis aus dem DialogFragment-Objekt. Sehr chaotisch und funktioniert nicht bei Konfigurationsänderungen (Orientierungsänderungen), da das DialogFragment den Verweis auf die Aktivität verliert.

Vielen Dank für jede Hilfe.


10
DialogFragments sind immer noch nur Fragmente. Ihr Ansatz ist tatsächlich die empfohlene Methode für Fragmente, um mit der Hauptaktivität zu sprechen. developer.android.com/guide/topics/fundamentals/…
Codierungsbenutzer

1
Dank dafür. Ich war sehr nah (wie Sie sagten). Das Bit, mit dem dieses verknüpfte Dokument mir geholfen hat, war die Verwendung von onAttach () und das Übertragen der Aktivität an einen Listener.
James Cross

2
@codinguser, @Styx - "dem DialogFragment einen Verweis auf die Aktivität geben" - dieses Detail ist ein wenig riskant, da sowohl das Activityals auch das DialogFragmentmöglicherweise neu erstellt werden. Die Verwendung des ActivityPassed to onAttach(Activity activity)ist der richtige und empfohlene Weg.
sstn

Antworten:


246

Verwenden myDialogFragment.setTargetFragment(this, MY_REQUEST_CODE)Sie diese Option an der Stelle, an der Sie den Dialog anzeigen. Wenn Ihr Dialog beendet ist, können Sie sie aufrufen getTargetFragment().onActivityResult(getTargetRequestCode(), ...)und onActivityResult()im enthaltenen Fragment implementieren .

Es scheint ein Missbrauch von zu sein onActivityResult(), zumal es überhaupt keine Aktivitäten beinhaltet. Aber ich habe gesehen, dass es von offiziellen Google-Leuten empfohlen wurde, und vielleicht sogar in den API-Demos. Ich denke, es ist das, wofür g/setTargetFragment()hinzugefügt wurde.


2
setTargetFragment erwähnt, dass der Anforderungscode für die Verwendung in onActivityResult vorgesehen ist. Ich denke, es ist in Ordnung, diesen Ansatz zu verwenden.
Giorgi

87
Was ist, wenn das Ziel eine Aktivität ist?
Fernando Gallego

9
Wenn das Ziel Aktivität ist, würde ich die Schnittstelle mit einer Methode wie "void onActivityResult2 (int requestCode, int resultCode, Intent data)" deklarieren und durch eine Aktivität implementieren. In DialogFragment einfach getActivity und suchen Sie nach dieser Schnittstelle und rufen Sie sie entsprechend auf.
Ruslan Yanchyshyn

4
Es ist keine gute Lösung. Nach dem Speichern und Wiederherstellen des Dialogfragmentstatus funktioniert es nicht mehr. LocalBroadcastManager ist in diesem Fall die beste Lösung.
Nik

4
@Nik Das stimmt einfach nicht. Es ist die beste Lösung. Es gibt kein Problem beim Speichern und Wiederherstellen des Status. Wenn Sie jemals ein Problem hatten, haben Sie den falschen Fragmentmanager verwendet. Das Zielfragment / der Aufrufer muss getChildFragmentManager () verwenden, um den Dialog anzuzeigen.
Der unglaubliche

140

Wie Sie hier sehen können , gibt es einen sehr einfachen Weg, dies zu tun.

Fügen DialogFragmentSie in Ihrem Interface einen Listener hinzu wie:

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

Fügen Sie dann einen Verweis auf diesen Listener hinzu:

private EditNameDialogListener listener;

Dies wird verwendet, um die Listener-Methode (n) zu "aktivieren" und um zu überprüfen, ob die übergeordnete Aktivität / das übergeordnete Fragment diese Schnittstelle implementiert (siehe unten).

In der Activity/ FragmentActivity/ Fragmentdas „genannt“ die DialogFragmentUmsetzung einfach diese Schnittstelle.

In Ihrem DialogFragmentalles , was Sie an dem Punkt hinzufügen müssen , wo Sie die entlassen möchten DialogFragmentund das Ergebnis zurück , ist dies:

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

Wo mEditText.getText().toString()ist, was an die Berufung zurückgegeben wird Activity.

Beachten Sie, dass Sie, wenn Sie etwas anderes zurückgeben möchten, einfach die Argumente ändern, die der Listener verwendet.

Schließlich sollten Sie überprüfen, ob die Schnittstelle tatsächlich von der übergeordneten Aktivität / dem übergeordneten Fragment implementiert wurde:

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

Diese Technik ist sehr flexibel und ermöglicht das Zurückrufen mit dem Ergebnis, auch wenn Sie den Dialog noch nicht schließen möchten.


13
Dies funktioniert gut mit Activity's und FragmentActivity' s, aber wenn ist der Anrufer ein Fragment?
Brais Gabin

1
Ich bin mir nicht sicher, ob ich dich vollständig verstehe. Aber es wird genauso funktionieren, wenn der Anrufer ein ist Fragment.
Assaf Gamliel

2
Wenn der Anrufer ein Fragmentwar, können Sie einige Dinge tun: 1. Übergeben Sie das Fragment als Referenz (möglicherweise keine gute Idee, da Sie möglicherweise Speicherlecks verursachen). 2. Verwenden Sie das FragmentManagerund rufen Sie auf findFragmentByIdoder findFragmentByTages werden die Fragmente abgerufen, die in Ihrer Aktivität vorhanden sind. Ich hoffe es hat geholfen. Ich wünsche ihnen einen wunderbaren Tag!
Assaf Gamliel

5
Das Problem bei diesem Ansatz ist, dass Fragmente Objekte nicht sehr gut behalten können, da sie neu erstellt werden sollen. Versuchen Sie beispielsweise, die Ausrichtung zu ändern. Das Betriebssystem erstellt das Fragment neu, aber die Instanz des Listeners ist nicht mehr verfügbar
Necronet

5
@LOG_TAG Schauen Sie sich die Antwort von @ Timmmm an. setTargetFragment()und getTargetFragment()sind magisch.
Brais Gabin

48

Es gibt eine viel einfachere Möglichkeit, ein Ergebnis von einem DialogFragment zu erhalten.

Zunächst müssen Sie in Ihrer Aktivität, Ihrem Fragment oder Ihrer FragmentAktivität die folgenden Informationen hinzufügen:

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

Das requestCodeist im Grunde Ihre int-Bezeichnung für das von Ihnen aufgerufene DialogFragment. Ich werde gleich zeigen, wie dies funktioniert. Der resultCode ist der Code, den Sie vom DialogFragment zurücksenden und der Ihrer aktuell wartenden Aktivität, Fragment oder FragmentActivity mitteilt, was passiert ist.

Der nächste Code ist der Aufruf des DialogFragments. Ein Beispiel ist hier:

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

Mit diesen drei Zeilen deklarieren Sie Ihr DialogFragment und legen einen requestCode fest (der das onActivityResult (...) aufruft, sobald der Dialog geschlossen wird, und Sie zeigen dann den Dialog an. So einfach ist das.

Jetzt müssen Sie in Ihrem DialogFragment nur eine Zeile direkt vor dem hinzufügen dismiss(), damit Sie einen resultCode an onActivityResult () zurücksenden.

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

Das ist es. Beachten Sie, dass der resultCode so definiert ist, wie int resultCodeich ihn resultCode = 1;in diesem Fall festgelegt habe.

Jetzt können Sie das Ergebnis Ihres DialogFragments an Ihre aufrufende Aktivität, Fragment oder FragmentActivity zurücksenden.

Es sieht auch so aus, als ob diese Informationen bereits veröffentlicht wurden, aber es wurde kein ausreichendes Beispiel angegeben, sodass ich dachte, ich würde mehr Details liefern.

EDIT 24.06.2016 Ich entschuldige mich für den irreführenden Code oben. Aber Sie können das Ergebnis mit Sicherheit nicht an die Aktivität zurückgeben, die als Linie betrachtet wird:

dialogFrag.setTargetFragment(this, 1);

setzt ein Ziel Fragmentund nicht Activity. Um dies zu tun, müssen Sie ein implementieren InterfaceCommunicator.

In Ihrem DialogFragmentSet eine globale Variable

public InterfaceCommunicator interfaceCommunicator;

Erstellen Sie eine öffentliche Funktion, um damit umzugehen

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

Dann , wenn Sie bereit sind , den Code wieder in die senden , Activitywenn die DialogFragmentLauf getan wird, fügen Sie einfach die Linie , bevor Sie dismiss();Ihre DialogFragment:

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

In Ihrer Aktivität müssen Sie jetzt zwei Dinge tun: Die erste besteht darin, die eine Codezeile zu entfernen, die nicht mehr anwendbar ist:

dialogFrag.setTargetFragment(this, 1);  

Dann implementieren Sie die Schnittstelle und Sie sind fertig. Sie können dies tun, indem Sie der implementsKlausel ganz oben in Ihrer Klasse die folgende Zeile hinzufügen :

public class MyClass Activity implements MyDialogFragment.InterfaceCommunicator

Und dann @Overridedie Funktion in der Aktivität,

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

Sie verwenden diese Schnittstellenmethode genauso wie die onActivityResult()Methode. Außer die Schnittstellenmethode ist für DialogFragmentsund die andere ist für Fragments.


4
Dieser Ansatz funktioniert nicht, wenn das Ziel Aktivität ist, da Sie das onActivityResult (von Ihrem DialogFragment) aufgrund der geschützten Zugriffsebene nicht aufrufen können.
Ruslan Yanchyshyn

2
Das stimmt einfach nicht. Ich verwende genau diesen Code in meinen Projekten. Dort habe ich es herausgezogen und es funktioniert einwandfrei. Bitte denken Sie daran, dass Sie bei diesem Problem mit der geschützten Zugriffsebene Ihre Zugriffsebene für jede Methode und Klasse bei Bedarf von geschützt auf privat oder öffentlich ändern können.
Brandon

6
Hallo, Sie sagen, Sie können dialogFrag.setTargetFragment(this, 1)von einer Aktivität aus aufrufen , aber diese Methode erhält ein Fragment als erstes Argument, sodass dies nicht umgewandelt werden konnte. Habe ich recht ?
MondKin

1
Ich werde in ein paar Antworten einige Antworten für Sie veröffentlichen, um die Aktivitäten zu erklären.
Brandon

1
@Swift @lcompare Sie müssen wahrscheinlich onAttach (Kontextkontext) in Ihrem DialogFragment überschreiben. Wie so:@Override public void onAttach(Context context) { super.onAttach(context); yourInterface = (YourInterface) context; }
lidkxx

20

Nun, es ist vielleicht zu spät, um zu antworten, aber hier ist, was ich getan habe, um Ergebnisse von der zurück zu bekommen DialogFragment. sehr ähnlich zu @ brandons Antwort. Hier rufe ich DialogFragmentvon einem Fragment aus an, platziere diesen Code einfach dort, wo du deinen Dialog aufrufst.

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

Wo categoryDialogist meine, DialogFragmentdie ich anrufen möchte und danach in Ihrer Implementierung dialogfragmentplatzieren Sie diesen Code, wo Sie Ihre Daten in Absicht setzen. Der Wert von resultCodeist 1, Sie können ihn festlegen oder systemdefiniert verwenden.

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

Jetzt ist es an der Zeit, zum aufrufenden Fragment zurückzukehren und diese Methode zu implementieren. Überprüfen Sie die Gültigkeit der Daten oder den Erfolg der Ergebnisse, wenn Sie dies mit resultCodeund requestCodein der if-Bedingung wünschen .

 @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

Unterschiedlicher Ansatz, damit ein Fragment bis zu seiner Aktivität kommunizieren kann :

1) Definieren Sie eine öffentliche Schnittstelle im Fragment und erstellen Sie eine Variable dafür

public OnFragmentInteractionListener mCallback;

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

2) Wandeln Sie die Aktivität in die Variable mCallback im Fragment um

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

3) Implementieren Sie den Listener in Ihre Aktivität

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

4) Überschreiben Sie die OnFragmentInteraction in der Aktivität

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

Weitere Informationen dazu: https://developer.android.com/training/basics/fragments/communicating.html


Danke, dass du es so gut zusammengefasst hast. Nur eine Anmerkung für andere, das Android Devs Tutorial schlägt vor, public void onAttachdas Fragment zu überschreiben und das Aktivitätscasting dort
durchzuführen

8

Ein einfacher Weg, den ich gefunden habe, war der folgende: Implementieren Sie dies ist Ihr dialogFragment,

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

Und dann erstellen Sie in der Aktivität, die das Dialogfragment aufgerufen hat, die entsprechende Funktion als solche:

 public void onUserSelectValue(String selectedValue) {

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

Der Toast soll zeigen, dass es funktioniert. Hat für mich gearbeitet.


Ich bin nicht sicher, ob es ein richtiger Weg ist, aber es funktioniert auf jeden
Fall

Besser verwenden Interfaceals hartkoppeln mit konkreten Klassen.
Waqaslam

6

Ich bin sehr überrascht zu sehen, dass niemand vorgeschlagen hat, lokale Sendungen für DialogFragmentdie ActivityKommunikation zu verwenden! Ich finde es so viel einfacher und sauberer als andere Vorschläge. Im Wesentlichen registrieren Sie sich, damit Sie Activitydie Sendungen abhören können, und Sie senden die lokalen Sendungen von Ihren DialogFragmentInstanzen. Einfach. Eine Schritt-für-Schritt-Anleitung zum Einrichten finden Sie hier .


2
Ich mag diese Lösung. Wird dies als gute oder bewährte Methode für Android angesehen?
Benjamin Scharbau

1
Das Tutorial hat mir sehr gut gefallen. Vielen Dank, dass Sie es veröffentlicht haben. Ich möchte hinzufügen, dass je nachdem, was Sie erreichen möchten, eine der beiden Methoden nützlicher sein kann als die andere. Ich würde die lokale Übertragungsroute vorschlagen, wenn Sie mehrere Eingaben / Ergebnisse haben, die vom Dialogfeld an die Aktivität zurückgesendet werden. Ich würde empfehlen, die Route onActivityResult zu verwenden, wenn Ihre Ausgabe sehr einfach ist. Um die Best-Practice-Frage zu beantworten, hängt es davon ab, was Sie erreichen möchten!
Brandon

1
@ AdilHussain Du hast recht. Ich ging davon aus, dass Menschen Fragmente für ihre Aktivitäten verwenden. Die Option setTargetFragment eignet sich hervorragend, wenn Sie mit einem Fragment und einem DialogFragment kommunizieren. Sie müssen jedoch die Broadcast-Methode verwenden, wenn es sich um eine Aktivität handelt, die das DialogFragment aufruft.
Brandon

3
Verwenden Sie aus Liebe zu Foo keine Sendungen! Es öffnet Ihre Anwendung für eine Reihe von Sicherheitsproblemen. Außerdem finde ich, dass die schlechteste Android-Anwendung, die ich habe, an Missbrauchssendungen arbeiten muss. Können Sie sich einen besseren Weg vorstellen, um Code völlig unbrauchbar zu machen? Jetzt muss ich Broadcast-Empfänger anstelle einer CLEAR-Codezeile ausrotten? Um klar zu sein, es gibt Verwendungszwecke für Broadcoasts, aber nicht in diesem Zusammenhang! NIE in diesem Zusammenhang! Es ist nur schlampig. Lokal oder nicht. Rückrufe sind alles was Sie brauchen.
StarWind0

1
Neben dem Guava EventBus ist der GreenRobot EventBus eine weitere Option . Ich habe den Guava EventBus nicht verwendet, aber den GreenRobot EventBus verwendet und gute Erfahrungen damit gemacht. Schön und einfach zu bedienen. Ein kleines Beispiel für die Architektur einer Android-Anwendung zur Verwendung des GreenRobot EventBus finden Sie hier .
Adil Hussain

3

Oder teilen Sie ViewModel wie hier gezeigt:

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

In meinem Fall musste ich Argumente an ein targetFragment übergeben. Aber ich habe die Ausnahme "Fragment bereits aktiv". Also habe ich in meinem DialogFragment eine Schnittstelle deklariert, die parentFragment implementiert hat. Wenn parentFragment ein DialogFragment gestartet hat, hat es sich selbst als TargetFragment festgelegt. Dann habe ich in DialogFragment angerufen

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

2

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

// Meine Aktivität

class MyActivity: AppCompatActivity(),FiltroDialogFragment.InterfaceCommunicator {

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

Ich hoffe es dient, wenn Sie verbessern können, bearbeiten Sie es bitte. Mein Englisch ist nicht sehr gut


In meinem Fall wird der Dialog aus einem Fragment erstellt, das keine Aktivität ist, sodass diese Lösung nicht funktioniert. Aber ich mag den Smiley, den du gesetzt hast :)
Ssenyonjo

0

Wenn Sie Argumente senden und das Ergebnis vom zweiten Fragment empfangen möchten, können Sie Fragment.setArguments verwenden, um diese Aufgabe auszuführen

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

Nur um es als eine der Optionen zu haben (da es noch niemand erwähnt hat) - Sie könnten einen Eventbus wie Otto benutzen. Im Dialog tun Sie also:

bus.post(new AnswerAvailableEvent(42));

Und lassen Sie Ihren Anrufer (Aktivität oder Fragment) es abonnieren:

@Subscribe public void answerAvailable(AnswerAvailableEvent event) {
   // TODO: React to the event somehow!
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.