Otrzymuj wynik z DialogFragment

232

Korzystam z DialogFragments do wielu rzeczy: wybieranie pozycji z listy, wprowadzanie tekstu.

Jaki jest najlepszy sposób na zwrócenie wartości (tj. Łańcucha lub elementu z listy) z powrotem do działania / fragmentu wywołującego?

Obecnie wprowadzam działanie wywołujące DismissListeneri przekazuję DialogFragment odwołanie do działania. Dialog wywołuje następnie OnDimissmetodę w działaniu, a działanie pobiera wynik z obiektu DialogFragment. Bardzo niechlujny i nie działa przy zmianie konfiguracji (zmianie orientacji), ponieważ DialogFragment traci odniesienie do działania.

Dziękuję za wszelką pomoc.

James Cross
źródło
10
DialogFragments to wciąż tylko fragmenty. Twoje podejście jest w rzeczywistości zalecanym sposobem wykorzystania fragmentów do rozmowy z główną działalnością. developer.android.com/guide/topics/fundamentals/…
codinguser
1
Dziękuję za to. Byłem bardzo blisko (jak powiedziałeś). To, w czym pomógł mi ten połączony dokument, polegało na użyciu onAttach () i przekazaniu działania do detektora.
James Cross
2
@codinguser, @Styx - „podając odwołanie do DialogFragment do działania” - ten szczegół jest trochę ryzykowny, ponieważ zarówno te, jak Activityi te DialogFragmentmogą zostać odtworzone. Korzystanie z Activityprzekazanego do onAttach(Activity activity)jest właściwym i zalecanym sposobem.
sstn

Odpowiedzi:

246

Użyj myDialogFragment.setTargetFragment(this, MY_REQUEST_CODE)z miejsca, w którym wyświetlasz okno dialogowe, a następnie po zakończeniu okna dialogowego, możesz z niego zadzwonić getTargetFragment().onActivityResult(getTargetRequestCode(), ...)i zaimplementowaćonActivityResult() w zawierającym go fragmencie.

Wydaje się, że jest to nadużycie onActivityResult(), zwłaszcza, że ​​w ogóle nie obejmuje działań. Ale widziałem to zalecane przez oficjalnych ludzi z Google, a może nawet w wersjach API. Myślę, że po to g/setTargetFragment()zostały dodane.

Timmmm
źródło
2
setTargetFragment wspomina, że ​​kod żądania jest przeznaczony do użycia w onActivityResult, więc myślę, że można użyć tego podejścia.
Giorgi
87
Co jeśli cel jest działaniem?
Fernando Gallego
9
Jeśli celem jest działanie, zadeklaruję interfejs za pomocą metody „void onActivityResult2 (int requestCode, int resultCode, dane intencji)” i zaimplementuję go za pomocą działania. W DialogFragment po prostu getActivity i sprawdź ten interfejs i odpowiednio go wywołaj.
Ruslan Yanchyshyn,
4
To nie jest dobre rozwiązanie. Nie będzie działać po zapisaniu i przywróceniu stanu fragmentu okna dialogowego. LocalBroadcastManager jest najlepszym rozwiązaniem w tym przypadku.
Nik
4
@Nik To po prostu nieprawda. To najlepsze rozwiązanie. Podczas zapisywania i przywracania stanu nie ma problemu. Jeśli kiedykolwiek wystąpił problem, użyłeś niewłaściwego menedżera fragmentów. Docelowy fragment / obiekt wywołujący musi użyć funkcji getChildFragmentManager (), aby wyświetlić okno dialogowe.
Niesamowity
140

Jak widać tutaj , jest to bardzo prosty sposób.

W twojej DialogFragmentdodać detektor interfejsu, takich jak:

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

Następnie dodaj odwołanie do tego odbiornika:

private EditNameDialogListener listener;

Będzie to wykorzystane do „aktywacji” metod detektora, a także do sprawdzenia, czy działanie nadrzędne / fragment implementuje ten interfejs (patrz poniżej).

W Activity/ FragmentActivity/ Fragmentże „wywołał”DialogFragment prostu zaimplementuj ten interfejs.

W DialogFragmentsumie musisz dodać w punkcie, w którym chcesz odrzucić DialogFragmenti zwrócić wynik:

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

Gdzie mEditText.getText().toString()jest to, co zostanie przekazane z powrotem do połączenia Activity.

Zauważ, że jeśli chcesz zwrócić coś innego, po prostu zmień argumenty, które przyjmuje słuchacz.

Na koniec powinieneś sprawdzić, czy interfejs został faktycznie zaimplementowany przez działanie / fragment nadrzędny:

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

Ta technika jest bardzo elastyczna i pozwala oddzwonić z wynikiem, nawet jeśli nie chcesz jeszcze zamknąć okna dialogowego.

Assaf Gamliel
źródło
13
Działa to świetnie z Activity„i FragmentActivity”, ale jeśli dzwoniący to Fragment?
Brais Gabin
1
Nie jestem pewien, czy cię w pełni rozumiem. Ale zadziała tak samo, jeśli dzwoniącym jest Fragment.
Assaf Gamliel
2
Jeśli osoba dzwoniąca była Fragment, możesz zrobić kilka rzeczy: 1. Przekaż fragment jako odniesienie (może nie być dobrym pomysłem, ponieważ możesz spowodować wyciek pamięci). 2. Użyj FragmentManageri wezwanie findFragmentByIdlub findFragmentByTagbędzie się fragmenty, które istnieją w swojej działalności. Mam nadzieję, że to pomogło. Miłego dnia!
Assaf Gamliel
5
Problem z tym podejściem polega na tym, że fragment nie jest zbyt dobry w zatrzymywaniu obiektu, ponieważ należy go odtworzyć, na przykład spróbuj zmienić orientację, system operacyjny odtworzy fragment, ale wystąpienie detektora nie będzie już dostępne
Necronet
5
@LOG_TAG spójrz na odpowiedź @ Timmmm. setTargetFragment()i getTargetFragment()są magiczne.
Brais Gabin
48

Istnieje znacznie prostszy sposób otrzymania wyniku z DialogFragment.

Po pierwsze, w swojej Aktywności, Fragmentie lub FragmentActivity musisz dodać następujące informacje:

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

Jest requestCodeto w zasadzie twoja etykieta int dla DialogFragment, który wywołałeś, pokażę, jak to działa za sekundę. ResultCode to kod, który odsyłasz z DialogFragment, informujący o bieżącym działaniu oczekującym, fragmencie lub fragmentActivity.

Następnym fragmentem kodu, który należy wprowadzić, jest wywołanie DialogFragment. Przykład jest tutaj:

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

Za pomocą tych trzech wierszy deklarujesz swój DialogFragment, ustawiając requestCode (który wywoła onActivityResult (...) po zamknięciu Dialogu, a następnie wyświetlasz okno dialogowe. To takie proste.

Teraz w DialogFragment musisz tylko dodać jeden wiersz bezpośrednio przed nim dismiss(), aby wysłać wynik z powrotem do onActivityResult ().

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

Otóż ​​to. Uwaga, wynik jest zdefiniowany jako int resultCodeustawiony resultCode = 1;w tym przypadku.

To wszystko, możesz teraz wysłać wynik DialogFragment z powrotem do swojej aktywności wywołującej, fragmentu lub aktywności fragmentu.

Wygląda na to, że ta informacja została wcześniej opublikowana, ale nie podano wystarczającego przykładu, więc pomyślałem, że podam więcej szczegółów.

EDIT 06.24.2016 Przepraszam za wprowadzający w błąd kod powyżej. Ale z pewnością nie możesz otrzymać wyniku z powrotem do działania, widząc to jako linię:

dialogFrag.setTargetFragment(this, 1);

wyznacza cel, Fragmenta nie Activity. Aby to zrobić, musisz użyć implementacji InterfaceCommunicator.

W twoim DialogFragmentzestawie zmienna globalna

public InterfaceCommunicator interfaceCommunicator;

Utwórz funkcję publiczną, aby ją obsłużyć

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

Wtedy, gdy jesteś gotowy do wysłania kodu z powrotem do Activitykiedy DialogFragmentodbywa działa, po prostu dodaj linię przed wami dismiss();swoimi DialogFragment:

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

W swojej działalności musisz teraz zrobić dwie rzeczy, po pierwsze, usunąć ten jeden wiersz kodu, który nie ma już zastosowania:

dialogFrag.setTargetFragment(this, 1);  

Następnie zaimplementuj interfejs i gotowe. Możesz to zrobić, dodając następujący wiersz do implementsklauzuli na samej górze klasy:

public class MyClass Activity implements MyDialogFragment.InterfaceCommunicator

A potem @Overridefunkcja w działaniu,

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

Używasz tej metody interfejsu tak samo, jak onActivityResult()metody. Z wyjątkiem tego, że metoda interfejsu służy do, DialogFragmentsa druga do Fragments.

Brandon
źródło
4
To podejście nie zadziała, jeśli celem jest Aktywność, ponieważ nie można wywołać jego onActivityResult (z DialogFragment) ze względu na chroniony poziom dostępu.
Ruslan Yanchyshyn,
2
To po prostu nieprawda. Używam dokładnie tego kodu w moich projektach. Właśnie z tego go wyciągnąłem i działa dobrze. Pamiętaj, że jeśli masz problem z chronionym poziomem dostępu, w razie potrzeby możesz zmienić poziom dostępu dla dowolnej metody i klasy z chronionej na prywatną lub publiczną.
Brandon,
6
Cześć, mówisz, że możesz zadzwonić dialogFrag.setTargetFragment(this, 1)z działania, ale ta metoda otrzymuje fragment jako pierwszy argument, więc nie można go rzucić. Czy mam rację ?
MondKin,
1
Wyślę kilka odpowiedzi dla was wszystkich w kilku, aby wyjaśnić rzeczy związane z aktywnością.
Brandon
1
@Swift @lcompare prawdopodobnie musisz zastąpić onAttach (kontekst kontekstowy) w DialogFragment. Tak jak:@Override public void onAttach(Context context) { super.onAttach(context); yourInterface = (YourInterface) context; }
lidkxx
20

Cóż, może już być za późno na odpowiedź, ale oto, co zrobiłem, aby uzyskać wyniki z DialogFragment. bardzo podobny do odpowiedzi @ brandon. DzwonięDialogFragment z fragmentu, po prostu umieść ten kod w miejscu, w którym wywołujesz okno dialogowe.

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

gdzie categoryDialogjest mój, do DialogFragmentktórego chcę zadzwonić, a następnie w swojej implementacji dialogfragmentumieść ten kod w miejscu, w którym zamierzasz ustawić swoje dane. Wartość resultCode1 można ustawić lub użyć opcji Zdefiniowane przez system.

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

teraz czas wrócić do fragmentu wywołującego i zaimplementować tę metodę. sprawdzenia prawidłowości danych lub spowodować sukces, jeśli chcesz się resultCodei requestCodejeśli warunek.

 @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
źródło
10

Inne podejście, aby umożliwić Fragmentowi komunikowanie się aż do jego Aktywności :

1) Zdefiniuj interfejs publiczny we fragmencie i utwórz dla niego zmienną

public OnFragmentInteractionListener mCallback;

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

2) Rzuć aktywność na zmienną mCallback we fragmencie

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

3) Zaimplementuj detektor w swojej działalności

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

4) Zastąp OnFragmentInteraction w działaniu

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

Więcej informacji na ten temat: https://developer.android.com/training/basics/fragments/communicating.html

porównać
źródło
Dzięki za tak dobre podsumowanie. Tylko jedna uwaga dla innych, samouczek Android Devs sugeruje przesłonić public void onAttachfragment i wykonać tam casting
Big_Chair
8

Znalazłem jeden prosty sposób: Zaimplementuj to jest twoje okno dialogowe Fragment,

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

A następnie w działaniu, które wywołało Fragment Dialogu, utwórz odpowiednią funkcję jako taką:

 public void onUserSelectValue(String selectedValue) {

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

Toast ma pokazać, że działa. Pracował dla mnie.

ZeWolfe15
źródło
Nie jestem pewien, czy jest to właściwy sposób, ale na pewno działa :)
soshial
Lepsze użycie Interfaceniż twarde połączenie z klasami betonu.
waqaslam
6

Jestem bardzo zaskoczony, widząc, że nikt nie zasugerował wykorzystania lokalnych transmisji DialogFragmentdo Activitykomunikacji! Uważam, że jest o wiele prostsze i czystsze niż inne sugestie. Zasadniczo rejestrujesz się, Activityaby wysłuchać transmisji i wysyłasz transmisje lokalne ze swoich DialogFragmentwystąpień. Prosty. Aby dowiedzieć się, jak to wszystko skonfigurować, zobacz krok po kroku. Zobacz tutaj .

Adil Hussain
źródło
2
Podoba mi się to rozwiązanie, czy jest to uważane za dobrą lub najlepszą praktykę w systemie Android?
Benjamin Scharbau,
1
Bardzo podobał mi się samouczek, dziękuję za opublikowanie go. Chcę dodać, że w zależności od tego, co próbujesz osiągnąć, każda metoda może być bardziej przydatna niż druga. Sugerowałbym lokalną trasę emisji, jeśli masz wiele danych wejściowych / wyników wysyłanych z powrotem do działania z okna dialogowego. Polecam użycie trasy onActivityResult, jeśli wyniki są bardzo proste / proste. Tak więc, aby odpowiedzieć na pytanie dotyczące najlepszych praktyk, zależy to od tego, co próbujesz osiągnąć!
Brandon,
1
@AdilHussain Masz rację. Przyjąłem założenie, że ludzie używają Fragmentów w ramach swoich Działań. Opcja setTargetFragment jest świetna, jeśli komunikujesz się z Fragmentem i DialogFragment. Ale musisz użyć metody rozgłaszania, gdy jest to działanie wywołujące DialogFragment.
Brandon,
3
Na miłość Foo, nie używaj transmisji !! Otwiera twoją aplikację na problem bezpieczeństwa. Uważam też, że najgorsza aplikacja na Androida, nad którą pracuję, nadawała nadużycia. Czy możesz wymyślić lepszy sposób, aby kod był całkowicie bezużyteczny? Teraz muszę wyodrębnić odbiorniki nadawcze zamiast linii CLEAR? Dla jasności istnieją zastosowania dla Broadcoastów, ale nie w tym kontekście! NIGDY w tym kontekście! Jest po prostu niechlujny. Lokalny czy nie. Oddzwoń to wszystko, czego potrzebujesz.
StarWind0
1
Oprócz Guava EventBus inną opcją jest GreenRobot EventBus . Nie korzystałem z Guava EventBus, ale korzystałem z GreenBobot EventBus i miałem z nim dobre doświadczenia. Miły i prosty w użyciu. Aby zobaczyć mały przykład architektury aplikacji Android na korzystanie z GreenBobot EventBus, zobacz tutaj .
Adil Hussain
3

Lub udostępnij ViewModel jak pokazano tutaj:

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
źródło
2

W moim przypadku musiałem przekazać argumenty do elementu docelowego. Ale dostałem wyjątek „Fragment już aktywny”. Zadeklarowałem więc interfejs w moim DialogFragment, który został zaimplementowany w ParentFragment. Gdy ParentFragment uruchomił DialogFragment, ustawia się jako TargetFragment. Następnie w DialogFragment zadzwoniłem

 ((Interface)getTargetFragment()).onSomething(selectedListPosition);
Lanitka
źródło
2

W Kotlinie

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

// Moja aktywność

class MyActivity: AppCompatActivity(),FiltroDialogFragment.InterfaceCommunicator {

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

Mam nadzieję, że to działa, jeśli możesz to poprawić, edytuj. Mój angielski nie jest zbyt dobry

Irvin Joao
źródło
W moim przypadku okno dialogowe jest tworzone z fragmentu nie będącego aktywnością, więc to rozwiązanie nie działa. Ale podoba mi się buźka, którą umieściłeś :)
Ssenyonjo
0

jeśli chcesz wysłać argumenty i otrzymać wynik z drugiego fragmentu, możesz użyć Fragment.setArguments, aby wykonać to zadanie

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");
}
Tak
źródło
-3

Aby mieć to jako jedną z opcji (ponieważ nikt jeszcze o tym nie wspominał) - możesz użyć autobusu zdarzeń takiego jak Otto. W oknie dialogowym wykonujesz:

bus.post(new AnswerAvailableEvent(42));

Poproś abonenta (działanie lub fragment), aby go subskrybował:

@Subscribe public void answerAvailable(AnswerAvailableEvent event) {
   // TODO: React to the event somehow!
}
Dennis K
źródło