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. } }); }
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 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. } }); } }