Komunikacja za pomocą fragmentów

Aby ponownie wykorzystać fragmenty, utwórz je jako całkowicie niezależne komponenty które definiują własny układ i działanie. Gdy już zdefiniujesz te zasady fragmentów wielokrotnego użytku, można je powiązać z aktywnością za pomocą logiki aplikacji, aby stworzyć ogólny złożony interfejs użytkownika.

Aby prawidłowo reagować na zdarzenia użytkownika i udostępniać informacje o stanie, często Potrzeba kanałów komunikacji między działaniem fragmentów lub między dwoma lub większą liczbą fragmentów. Aby zapewnić niezależne fragmenty, nie mogą łączyć się bezpośrednio z innymi fragmentami lub z aktywnością gospodarza.

W bibliotece Fragment dostępne są 2 opcje komunikacji: ViewModel i fragment Result API (interfejs API wyników). Zalecana opcja zależy od przypadku użycia. Do udostępnienia trwałych danych z niestandardowymi interfejsami API, należy użyć interfejsu ViewModel. Dla: jednorazowego wyniku z danymi, które można umieścić w Bundle, użyj fragmentu Result API (interfejs API wyników).

W sekcjach poniżej dowiesz się, jak korzystać z elementu ViewModel i fragmentu z fragmentem Interfejs Result API do komunikacji między fragmentami a działaniami.

Udostępnianie danych za pomocą modelu widoku danych

ViewModel to idealny wybór, gdy musisz udostępniać dane między w przypadku wielu fragmentów lub między fragmentami a ich aktywnością hosta. ViewModel obiekty przechowują zarządzać danymi interfejsu. Więcej informacji o ViewModel: Omówienie modelu widoku danych.

Udostępniaj dane aktywności gospodarza

W niektórych przypadkach może zaistnieć konieczność współdzielenia danych między fragmentami i aktywność gospodarza. Możesz na przykład przełączyć globalny interfejs na podstawie interakcji w obrębie fragmentu.

Weź pod uwagę te ItemViewModel:

Kotlin

class ItemViewModel : ViewModel() {
    private val mutableSelectedItem = MutableLiveData<Item>()
    val selectedItem: LiveData<Item> get() = mutableSelectedItem

    fun selectItem(item: Item) {
        mutableSelectedItem.value = item
    }
}

Java

public class ItemViewModel extends ViewModel {
    private final MutableLiveData<Item> selectedItem = new MutableLiveData<Item>();
    public void selectItem(Item item) {
        selectedItem.setValue(item);
    }
    public LiveData<Item> getSelectedItem() {
        return selectedItem;
    }
}

W tym przykładzie przechowywane dane są spakowane MutableLiveData. LiveData dostosowuje się do cyklu życia obserwowanej klasy posiadacza danych. MutableLiveData przyjmuje, że wartość jest została zmieniona. Więcej informacji o LiveData: Przegląd LiveData.

Zarówno Twój fragment, jak i jego aktywność hosta mogą pobierać udostępnioną instancję dla ViewModel z zakresem aktywności, przekazując aktywność do funkcji ViewModelProvider. za pomocą konstruktora. ViewModelProvider obsługuje tworzenie instancji ViewModel lub pobrać, jeśli już istnieje. Oba komponenty mogą obserwować modyfikować te dane.

Kotlin

class MainActivity : AppCompatActivity() {
    // Using the viewModels() Kotlin property delegate from the activity-ktx
    // artifact to retrieve the ViewModel in the activity scope.
    private val viewModel: ItemViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel.selectedItem.observe(this, Observer { item ->
            // Perform an action with the latest item data.
        })
    }
}

class ListFragment : Fragment() {
    // Using the activityViewModels() Kotlin property delegate from the
    // fragment-ktx artifact to retrieve the ViewModel in the activity scope.
    private val viewModel: ItemViewModel by activityViewModels()

    // Called when the item is clicked.
    fun onItemClicked(item: Item) {
        // Set a new item.
        viewModel.selectItem(item)
    }
}

Java

public class MainActivity extends AppCompatActivity {
    private ItemViewModel viewModel;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        viewModel = new ViewModelProvider(this).get(ItemViewModel.class);
        viewModel.getSelectedItem().observe(this, item -> {
            // Perform an action with the latest item data.
        });
    }
}

public class ListFragment extends Fragment {
    private ItemViewModel viewModel;

    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        viewModel = new ViewModelProvider(requireActivity()).get(ItemViewModel.class);
        ...
        items.setOnClickListener(item -> {
            // Set a new item.
            viewModel.select(item);
        });
    }
}

Udostępnianie danych między fragmentami

Co najmniej dwa fragmenty w ramach tej samej aktywności często muszą komunikować się z i integrację społeczną. Wyobraź sobie na przykład jeden fragment, który zawiera listę inny, który pozwala użytkownikowi stosować do listy różne filtry. Wdrożenie tego przypadku nie jest proste bez fragmentów ale wtedy przestajemy się komunikować, niezależne od siebie. Dodatkowo oba fragmenty muszą obsługiwać scenariusz gdzie drugi fragment nie jest jeszcze utworzony ani widoczny.

Te fragmenty mogą współdzielić zasób ViewModel za pomocą zakresu aktywności do obsługi tej komunikacji. Udostępniając ViewModel w ten sposób, fragmenty nie muszą się o sobie dowiedzieć, a aktywność nie musi nic robić w celu ułatwienia komunikacji.

Poniższy przykład pokazuje, jak dwa fragmenty mogą korzystać z udostępnianego ViewModel do komunikacji:

Kotlin

class ListViewModel : ViewModel() {
    val filters = MutableLiveData<Set<Filter>>()

    private val originalList: LiveData<List<Item>>() = ...
    val filteredList: LiveData<List<Item>> = ...

    fun addFilter(filter: Filter) { ... }

    fun removeFilter(filter: Filter) { ... }
}

class ListFragment : Fragment() {
    // Using the activityViewModels() Kotlin property delegate from the
    // fragment-ktx artifact to retrieve the ViewModel in the activity scope.
    private val viewModel: ListViewModel by activityViewModels()
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewModel.filteredList.observe(viewLifecycleOwner, Observer { list ->
            // Update the list UI.
        }
    }
}

class FilterFragment : Fragment() {
    private val viewModel: ListViewModel by activityViewModels()
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewModel.filters.observe(viewLifecycleOwner, Observer { set ->
            // Update the selected filters UI.
        }
    }

    fun onFilterSelected(filter: Filter) = viewModel.addFilter(filter)

    fun onFilterDeselected(filter: Filter) = viewModel.removeFilter(filter)
}

Java

public class ListViewModel extends ViewModel {
    private final MutableLiveData<Set<Filter>> filters = new MutableLiveData<>();

    private final LiveData<List<Item>> originalList = ...;
    private final LiveData<List<Item>> filteredList = ...;

    public LiveData<List<Item>> getFilteredList() {
        return filteredList;
    }

    public LiveData<Set<Filter>> getFilters() {
        return filters;
    }

    public void addFilter(Filter filter) { ... }

    public void removeFilter(Filter filter) { ... }
}

public class ListFragment extends Fragment {
    private ListViewModel viewModel;

    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        viewModel = new ViewModelProvider(requireActivity()).get(ListViewModel.class);
        viewModel.getFilteredList().observe(getViewLifecycleOwner(), list -> {
            // Update the list UI.
        });
    }
}

public class FilterFragment extends Fragment {
    private ListViewModel viewModel;

    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        viewModel = new ViewModelProvider(requireActivity()).get(ListViewModel.class);
        viewModel.getFilters().observe(getViewLifecycleOwner(), set -> {
            // Update the selected filters UI.
        });
    }

    public void onFilterSelected(Filter filter) {
        viewModel.addFilter(filter);
    }

    public void onFilterDeselected(Filter filter) {
        viewModel.removeFilter(filter);
    }
}

Oba fragmenty wykorzystują aktywność hosta jako zakres dla ViewModelProvider Fragmenty mają ten sam zakres, więc otrzymują tę samą instancję ViewModel, która umożliwia komunikację między nimi w obie strony.

Udostępniaj dane między fragmentem nadrzędnym i podrzędnym

Podczas pracy z fragmentami podrzędnymi fragment nadrzędny i jego element podrzędny fragmenty kodu mogą wymagać współdzielenia danych między sobą. Aby udostępniać dane między dla tych fragmentów, użyj fragmentu nadrzędnego jako zakresu ViewModel, jak pokazano w tym przykładzie:

Kotlin

class ListFragment: Fragment() {
    // Using the viewModels() Kotlin property delegate from the fragment-ktx
    // artifact to retrieve the ViewModel.
    private val viewModel: ListViewModel by viewModels()
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewModel.filteredList.observe(viewLifecycleOwner, Observer { list ->
            // Update the list UI.
        }
    }
}

class ChildFragment: Fragment() {
    // Using the viewModels() Kotlin property delegate from the fragment-ktx
    // artifact to retrieve the ViewModel using the parent fragment's scope
    private val viewModel: ListViewModel by viewModels({requireParentFragment()})
    ...
}

Java

public class ListFragment extends Fragment {
    private ListViewModel viewModel;

    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        viewModel = new ViewModelProvider(this).get(ListViewModel.class);
        viewModel.getFilteredList().observe(getViewLifecycleOwner(), list -> {
            // Update the list UI.
        }
    }
}

public class ChildFragment extends Fragment {
    private ListViewModel viewModel;
    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        viewModel = new ViewModelProvider(requireParentFragment()).get(ListViewModel.class);
        ...
    }
}

Określanie zakresu modelu widoku danych do wykresu nawigacji

Jeśli korzystasz z biblioteki nawigacji, możesz też zakres ViewModel do cyklu życia NavBackStackEntry Dla: np. pole ViewModel może być ograniczone do zakresu NavBackStackEntry dla zasobu ListFragment:

Kotlin

class ListFragment: Fragment() {
    // Using the navGraphViewModels() Kotlin property delegate from the fragment-ktx
    // artifact to retrieve the ViewModel using the NavBackStackEntry scope.
    // R.id.list_fragment == the destination id of the ListFragment destination
    private val viewModel: ListViewModel by navGraphViewModels(R.id.list_fragment)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewModel.filteredList.observe(viewLifecycleOwner, Observer { item ->
            // Update the list UI.
        }
    }
}

Java

public class ListFragment extends Fragment {
    private ListViewModel viewModel;

    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
    NavController navController = NavHostFragment.findNavController(this);
        NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.list_fragment)

        viewModel = new ViewModelProvider(backStackEntry).get(ListViewModel.class);
        viewModel.getFilteredList().observe(getViewLifecycleOwner(), list -> {
            // Update the list UI.
        }
    }
}

Więcej informacji o ograniczaniu zakresu ViewModel do NavBackStackEntry znajdziesz w artykule Wchodź w programowe interakcje z komponentem Nawigacja.

Uzyskiwanie wyników za pomocą interfejsu Fragment Result API

W niektórych przypadkach może być konieczne przekazanie jednorazowej wartości między dwoma fragmentami lub między fragmentem a jego aktywnością hosta. Możesz na przykład mieć który odczytuje kody QR i przekazuje dane z powrotem do poprzedniego fragmentu.

We fragmentach Fragment w wersji 1.3.0 lub nowszej co FragmentManager implements FragmentResultOwner Oznacza to, że FragmentManager może działać jako centralny magazyn fragmentów wyników. Dzięki tej zmianie komponenty mogą komunikować się ze sobą za pomocą przez ustawianie wyników fragmentów i słuchanie ich, bez konieczności te komponenty mają do siebie bezpośrednie odwołania.

Przekazywanie wyników między fragmentami

Aby przekazać dane z powrotem do fragmentu A z fragmentu B, najpierw ustaw detektor wyników na fragmencie A, czyli fragmencie, który otrzymuje wynik. Zadzwoń do nas setFragmentResultListener() we fragmencie „FragmentManager” fragmentu A, jak w tym przykładzie:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Use the Kotlin extension in the fragment-ktx artifact.
    setFragmentResultListener("requestKey") { requestKey, 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.
    }
}

Java

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getParentFragmentManager().setFragmentResultListener("requestKey", this, new FragmentResultListener() {
        @Override
        public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle bundle) {
            // We use a String here, but any type that can be put in a Bundle is supported.
            String result = bundle.getString("bundleKey");
            // Do something with the result.
        }
    });
}
fragment b wysyła dane do fragmentu a za pomocą FragmentManagera.
Rysunek 1. Fragment B wysyła dane do fragmentu A za pomocą tagu FragmentManager.

We fragmencie B, który zapewnia wynik, ustaw go w tym samym FragmentManager przy użyciu tego samego elementu requestKey. Możesz zrobić więc korzystając z setFragmentResult() Interfejs API:

Kotlin

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

Java

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Bundle result = new Bundle();
        result.putString("bundleKey", "result");
        getParentFragmentManager().setFragmentResult("requestKey", result);
    }
});

Fragment A otrzymuje następnie wynik i wykonuje wywołanie zwrotne detektora gdy fragment STARTED

Możesz mieć tylko 1 detektor i jeden wynik dla danego klucza. Jeśli dzwonisz setFragmentResult() więcej niż raz dla tego samego klucza, a jeśli detektor nie jest STARTED, system zastępuje wszystkie oczekujące wyniki zaktualizowanymi wynik.

Jeśli ustawisz detektor, który nie ma odpowiedniego detektora, wynik będzie przechowywany w FragmentManager, dopóki nie ustawisz detektora z ten sam klucz. Gdy słuchacze otrzyma wynik i uruchomi onFragmentResult() – wynik został wyczyszczony. To zachowanie z dwiema głównymi konsekwencjami:

  • Fragmenty na tylnej części stosu nie otrzymują wyników, dopóki nie zostaną wystrzeliły i są STARTED.
  • Jeśli fragment nasłuchujący wyniku ma stan STARTED, gdy ten wynik jest ustawiony: wywołanie zwrotne detektora jest następnie uruchamiane natychmiast.
.

Wyniki dotyczące fragmentów testowych

Używaj FragmentScenario aby przetestować wywołania setFragmentResult() i setFragmentResultListener(). Stwórz scenariusz dla testowanego fragmentu, wykorzystując w tym celu funkcję launchFragmentInContainer lub launchFragment, a następnie ręcznie wywołać metodę, która nie jest testowana.

Aby przetestować funkcję setFragmentResultListener(), utwórz scenariusz z fragment, który wywołuje funkcję setFragmentResultListener(). Następnie Zadzwoń bezpośrednio do setFragmentResult() i sprawdź wynik:

@Test
fun testFragmentResultListener() {
    val scenario = launchFragmentInContainer<ResultListenerFragment>()
    scenario.onFragment { fragment ->
        val expectedResult = "result"
        fragment.parentFragmentManager.setFragmentResult("requestKey", bundleOf("bundleKey" to expectedResult))
        assertThat(fragment.result).isEqualTo(expectedResult)
    }
}

class ResultListenerFragment : Fragment() {
    var result : String? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Use the Kotlin extension in the fragment-ktx artifact.
        setFragmentResultListener("requestKey") { requestKey, bundle ->
            result = bundle.getString("bundleKey")
        }
    }
}

Aby przetestować funkcję setFragmentResult(), utwórz scenariusz z fragmentem, który utworzy parametr połączenie z numerem setFragmentResult(). Następnie zadzwoń pod numer setFragmentResultListener() i sprawdź wynik:

@Test
fun testFragmentResult() {
    val scenario = launchFragmentInContainer<ResultFragment>()
    lateinit var actualResult: String?
    scenario.onFragment { fragment ->
        fragment.parentFragmentManager
                .setFragmentResultListener("requestKey") { requestKey, bundle ->
            actualResult = bundle.getString("bundleKey")
        }
    }
    onView(withId(R.id.result_button)).perform(click())
    assertThat(actualResult).isEqualTo("result")
}

class ResultFragment : Fragment(R.layout.fragment_result) {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        view.findViewById(R.id.result_button).setOnClickListener {
            val result = "result"
            // Use the Kotlin extension in the fragment-ktx artifact.
            setFragmentResult("requestKey", bundleOf("bundleKey" to result))
        }
    }
}

Przekazywanie wyników między fragmentami nadrzędnymi i podrzędnymi

Aby przekazać wynik z fragmentu podrzędnego do elementu nadrzędnego, użyj elementu getChildFragmentManager() z fragmentu nadrzędnego zamiast getParentFragmentManager() przy połączeniu z: setFragmentResultListener().

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Set the listener on the child fragmentManager.
    childFragmentManager.setFragmentResultListener("requestKey") { key, bundle ->
        val result = bundle.getString("bundleKey")
        // Do something with the result.
    }
}

Java

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Set the listener on the child fragmentManager.
    getChildFragmentManager()
        .setFragmentResultListener("requestKey", this, new FragmentResultListener() {
            @Override
            public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle bundle) {
                String result = bundle.getString("bundleKey");
                // Do something with the result.
            }
        });
}
fragment podrzędny może użyć FragmentManagera do wysyłania wyniku
            do elementu nadrzędnego
Rys. 2 Fragment podrzędny może korzystać z FragmentManager, aby wysłać wynik do elementu nadrzędnego.

Fragment podrzędny ustawia wynik na jego FragmentManager. Rodzic następnie otrzymuje wynik, gdy fragment będzie miał wartość STARTED:

Kotlin

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

Java

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Bundle result = new Bundle();
        result.putString("bundleKey", "result");
        // The child fragment needs to still set the result on its parent fragment manager.
        getParentFragmentManager().setFragmentResult("requestKey", result);
    }
});

Otrzymuj wyniki w aktywności hosta

Aby otrzymywać wyniki fragmentów w aktywności hosta, ustaw detektor wyników w menedżerze fragmentów za pomocą getSupportFragmentManager()

Kotlin

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        supportFragmentManager
                .setFragmentResultListener("requestKey", this) { requestKey, 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.
        }
    }
}

Java

class MainActivity extends AppCompatActivity {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getSupportFragmentManager().setFragmentResultListener("requestKey", this, new FragmentResultListener() {
            @Override
            public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle bundle) {
                // We use a String here, but any type that can be put in a Bundle is supported.
                String result = bundle.getString("bundleKey");
                // Do something with the result.
            }
        });
    }
}