Czy istnieje metoda, która działa jak fragment początkowy dla wyniku?

95

Obecnie mam fragment w nakładce. Służy do logowania się do usługi. W aplikacji na telefon każdy krok, który chcę pokazać w nakładce, to własne ekrany i czynności. Istnieją 3 części procesu logowania, a każda z nich miała własne działanie, które zostało wywołane za pomocą metody startActivityForResult ().

Teraz chcę zrobić to samo, używając fragmentów i nakładki. Nakładka pokaże fragment odpowiadający każdej czynności. Problem polega na tym, że te fragmenty są hostowane w działaniu w Honeycomb API. Mogę uruchomić pierwszy fragment, ale potem muszę uruchomićActivityForResult (), co nie jest możliwe. Czy jest coś podobnego do startFragmentForResult (), w którym mogę wykopać nowy fragment i czy po zakończeniu zwrócić wynik do poprzedniego fragmentu?

CACuzcatlan
źródło

Odpowiedzi:

58

Wszystkie fragmenty znajdują się w Aktywnościach. Rozpoczęcie fragmentu w celu uzyskania wyniku nie ma większego sensu, ponieważ działanie, które zawiera, zawsze ma do niego dostęp i odwrotnie. Jeśli fragment musi przekazać wynik, może uzyskać dostęp do jego działania, ustawić wynik i zakończyć go. W przypadku zamiany fragmentów w pojedynczym działaniu, działanie jest nadal dostępne dla obu fragmentów, a cała przekazywana wiadomość może po prostu przejść przez działanie.

Pamiętaj tylko, że zawsze masz komunikację między fragmentem a jego aktywnością. Rozpoczynanie i kończenie na wyniku to mechanizm komunikacji między Działaniami - Działania mogą następnie delegować wszelkie niezbędne informacje do swoich Fragmentów.

LeffelMania
źródło
11
Dodatkowo, ładując jeden fragment z drugiego, możesz ustawić fragment docelowy i odwołać się do metody fragmentu nadrzędnego, onActivityResultjeśli chcesz.
PJL,
4
Twoja odpowiedź jest niekompletna, fragmenty wykorzystują cykl życia, podobnie jak czynności. Nie możemy więc po prostu przekazywać argumentów metodami, ale powinniśmy używać pakietów do przekazywania wartości. Nawet jeśli fragment ustawił gdzieś wartość, musimy wiedzieć, kiedy skończył. Czy poprzednie fragmenty powinny otrzymać wartość po uruchomieniu / wznowieniu? To jest pomysł. Ale nie ma właściwego sposobu na przechowywanie wartości, fragment mógłby zostać wywołany przez wiele innych fragmentów / działań.
Loenix
1
cóż, sensowne byłoby wywołanie „fragmentu początkowego dla wyniku”, ponieważ dałoby to znacznie większą elastyczność -> po co fragment, tak jak dziecko miałoby wiedzieć o swoim rodzicu? nie byłoby dużo czystsze, gdyby fragment po prostu wykonał swoją pracę i zwrócił wynik, niezależnie od tego, w jakiej aktywności żyje. Szczególnie w kontekście aplikacji z pojedynczą aktywnością.
daneejela
A co z potencjalnymi wyciekami pamięci?
daneejela
60

Jeśli chcesz, istnieje kilka metod komunikacji między fragmentami,

setTargetFragment(Fragment fragment, int requestCode)
getTargetFragment()
getTargetRequestCode()

Możesz oddzwonić za pomocą tych.

Fragment invoker = getTargetFragment();
if(invoker != null) {
    invoker.callPublicMethod();
}
nagoya0
źródło
Nie potwierdziłem tego, ale może działa. Dlatego należy uważać na wycieki pamięci spowodowane przez odwołania cykliczne przez nadużywanie setTargetFragment().
nagoya0
3
Jak dotąd najlepsze rozwiązanie! Działa świetnie, gdy masz jedno Aktywność i stos fragmentów, w przypadku których próba znalezienia odpowiedniego fragmentu do powiadomienia byłaby koszmarem. Dzięki
Damien Praca
1
@ userSeven7s to nie będzie działać dla wymienić przypadek, ale powinien działać na ukryj przypadku
Muhammad Babar
1
@ nagoya0 robi to lepiej, jeśli użyjemy WeakReference dla fragmentu docelowego
quangson91
Chociaż to działa, setTargetFragmentjest obecnie przestarzałe. Zobacz setResultListenerw stackoverflow.com/a/61881149/2914140 tutaj.
CoolMind
11

Możemy po prostu udostępniać ten sam ViewModel między fragmentami

SharedViewModel

import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel

class SharedViewModel : ViewModel() {

    val stringData: MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }

}

FirstFragment

import android.arch.lifecycle.Observer
import android.os.Bundle
import android.arch.lifecycle.ViewModelProviders
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

class FirstFragment : Fragment() {

    private lateinit var sharedViewModel: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        activity?.run {
            sharedViewModel = ViewModelProviders.of(activity).get(SharedViewModel::class.java)
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_first, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        sharedViewModel.stringData.observe(this, Observer { dateString ->
            // get the changed String
        })

    }

}

SecondFragment

import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGrou

class SecondFragment : Fragment() {

    private lateinit var sharedViewModel: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        activity?.run {
            sharedViewModel = ViewModelProviders.of(activity).get(SharedViewModel::class.java)
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_first, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        changeString()
    }

    private fun changeString() {
        sharedViewModel.stringData.value = "Test"
    }

}
Levon Petrosyan
źródło
3
Cześć Levon, myślę, że powyższy kod nie będzie miał tego samego egzemplarza modelu widoku. Przekazujesz ten (fragment) dla ViewModelProviders.of (this) .get (SharedViewModel :: class.java). Spowoduje to utworzenie dwóch oddzielnych instancji dla fragmentów. Musisz przekazać aktywność ViewModelProviders.of (activity) .get (SharedViewModel :: class.java)
Shailendra Patil
@ShailendraPatil Niezły chwyt, naprawię to teraz.
Levon Petrosyan
6

Niedawno Google właśnie dodał nową możliwość, dzięki FragmentManagerktórej FragmentManagermoże działać jako centralny magazyn wyników fragmentów. Możemy łatwo przekazywać dane między fragmentami.

Początkowy fragment.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Use the Kotlin extension in the fragment-ktx artifact
    setResultListener("requestKey") { key, bundle ->
        // We use a String here, but any type that can be put in a Bundle is supported
        val result = bundle.getString("bundleKey")
        // Do something with the result...
    }
}

Fragment, dla którego chcemy odzyskać wynik.

button.setOnClickListener {
    val result = "result"
    // Use the Kotlin extension in the fragment-ktx artifact
    setResult("requestKey", bundleOf("bundleKey" to result))
}

Fragment pochodzi z oficjalnych dokumentów Google. https://developer.android.com/training/basics/fragments/pass-data-between#kotlin

Na podstawie danych tej odpowiedzi funkcja ta jest nadal alphaaktualna. Możesz to wypróbować, korzystając z tej zależności.

androidx.fragment:fragment:1.3.0-alpha05
Boonya Kitpitak
źródło
4

Moje 2 centy.

Przełączam się między fragmentami, zamieniając stary fragment na nowy za pomocą funkcji ukryj i pokaż / dodaj (istniejący / nowy). Więc ta odpowiedź jest dla deweloperów, którzy używają fragmentów tak jak ja.

Następnie używam tej onHiddenChangedmetody, aby wiedzieć, że stary fragment został przełączony z powrotem na nowy. Zobacz kod poniżej.

Przed opuszczeniem nowego fragmentu ustawiłem wynik w globalnym parametrze, o który będzie pytał stary fragment. To bardzo naiwne rozwiązanie.

@Override
public void onHiddenChanged(boolean hidden) {
    super.onHiddenChanged(hidden);
    if (hidden) return;
    Result result = Result.getAndReset();
    if (result == Result.Refresh) {
        refresh();
    }
}

public enum Result {
    Refresh;

    private static Result RESULT;

    public static void set(Result result) {
        if (RESULT == Refresh) {
            // Refresh already requested - no point in setting anything else;
            return;
        }
        RESULT = result;
    }

    public static Result getAndReset() {
        Result result = RESULT;
        RESULT = null;
        return result;
    }
}
AlikElzin-kilaka
źródło
Co to jest getAndReset()metoda?
EpicPandaForce
Czy nie jest również onResume()wywoływany na pierwszym fragmencie, gdy drugi jest odrzucany?
PJ_Finnegan
2

W swoim fragmencie możesz wywołać metodę getActivity (). To da ci dostęp do aktywności, która utworzyła fragment. Stamtąd możesz wywołać metodę dostosowywania, aby ustawić wartości lub przekazać wartości.

Summved Jain
źródło
1

Istnieje biblioteka Androida - FlowR, która umożliwia uruchamianie fragmentów wyników.

Rozpoczynanie fragmentu dla wyniku.

Flowr.open(RequestFragment.class)
    .displayFragmentForResults(getFragmentId(), REQUEST_CODE);

Obsługa prowadzi do fragmentu wywołującego.

@Override
protected void onFragmentResults(int requestCode, int resultCode, Bundle data) {
    super.onFragmentResults(requestCode, resultCode, data);

    if (requestCode == REQUEST_CODE) {
        if (resultCode == Activity.RESULT_OK) {
            demoTextView.setText("Result OK");
        } else {
            demoTextView.setText("Result CANCELED");
        }
    }
}

Ustawienie wyniku w pliku Fragment.

Flowr.closeWithResults(getResultsResponse(resultCode, resultData));
Ragunath Jawahar
źródło
1

Rozwiązanie wykorzystujące interfejsy (i Kotlin). Podstawową ideą jest zdefiniowanie interfejsu wywołania zwrotnego, zaimplementowanie go w swoim działaniu, a następnie wywołanie go z fragmentu.

Najpierw utwórz interfejs ActionHandler:

interface ActionHandler {
    fun handleAction(actionCode: String, result: Int)
}

Następnie zadzwoń do tego od swojego dziecka (w tym przypadku twojego fragmentu):

companion object {
    const val FRAGMENT_A_CLOSED = "com.example.fragment_a_closed"
}

fun closeFragment() {
    try {
        (activity as ActionHandler).handleAction(FRAGMENT_A_CLOSED, 1234)
    } catch (e: ClassCastException) {
        Timber.e("Calling activity can't get callback!")
    }
    dismiss()
}

Na koniec zaimplementuj to u swojego rodzica, aby otrzymać oddzwonienie (w tym przypadku Twoja aktywność):

class MainActivity: ActionHandler { 
    override fun handleAction(actionCode: String, result: Int) {
        when {
            actionCode == FragmentA.FRAGMENT_A_CLOSED -> {
                doSomething(result)
            }
            actionCode == FragmentB.FRAGMENT_B_CLOSED -> {
                doSomethingElse(result)
            }
            actionCode == FragmentC.FRAGMENT_C_CLOSED -> {
                doAnotherThing(result)
            }
        }
    }
Jake Lee
źródło
0

Najłatwiejszym sposobem przekazania danych z powrotem jest użycie metody setArgument (). Na przykład masz fragment1, który wywołuje fragment2, który wywołuje fragment3, fragment1 -> framgnet2 -> fargment3

We fragmencie 1

public void navigateToFragment2() {
    if (fragmentManager == null) return;

    Fragment2 fragment = Fragment2.newInstance();
    String tag = "Fragment 2 here";
    fragmentManager.beginTransaction()
            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
            .add(R.id.flContent, fragment, tag)
            .addToBackStack(null)
            .commitAllowingStateLoss();
}

We fragmencie 2 nazywamy fragment3 jak zwykle

private void navigateToFragment3() {
    if (fragmentManager == null) return;
    Fragment3 fragment = new Fragment3();
    fragmentManager.beginTransaction()
            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
            .replace(R.id.flContent, fragment, tag)
            .addToBackStack(null)
            .commit();
}

Kiedy zakończyliśmy nasze zadanie we fragmencie3, teraz nazywamy to:

FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
if (fragmentManager == null) return;
fragmentManager.popBackStack();
Bundle bundle = new Bundle();
bundle.putString("bundle_filter", "data");
fragmentManager.findFragmentByTag("Fragment 2 here").setArguments(bundle);

Teraz we fragmencie2 możemy łatwo wywołać argumenty

@Override
public void onResume() {
    super.onResume();
    Bundle rgs = getArguments();
    if (args != null) 
        String data = rgs.getString("bundle_filter");
}
Kirk_hehe
źródło
Uważaj z tym, może zadziałać w niektórych przypadkach, ale jeśli twój fragment miał oryginalne argumenty (w tym przykładzie „fragment 2”), nadpisałoby to oryginalne argumenty użyte do utworzenia fragmentu, co może prowadzić do niespójnego stanu, jeśli fragment jest zniszczony i odtworzony.
Juan
0

Inną rzeczą, którą możesz zrobić w zależności od architektury, jest użycie udostępnionego ViewModel między fragmentami. Więc w moim przypadku FragmentA jest formularzem, a FragmentB jest widokiem wyboru elementu, w którym użytkownik może wyszukać i wybrać element, przechowując go w ViewModel. Kiedy wrócę do Fragmentu, informacje są już zapisane!

Arjun
źródło