Per riutilizzare i frammenti, creali come componenti completamente autonomi che definiscono il proprio layout e comportamento. Una volta definiti questi frammenti riutilizzabili, puoi associarli a un'attività e connetterli alla logica dell'applicazione per realizzare l'interfaccia utente composita complessiva.
Per reagire in modo adeguato agli eventi utente e condividere informazioni sullo stato, spesso devi disporre di canali di comunicazione tra un'attività e i suoi frammenti o tra due o più frammenti. Per mantenere i frammenti autonomi, non fare in modo che comunicano direttamente con altri frammenti o con la relativa attività host.
La libreria Fragment
offre due opzioni per la comunicazione: un elemento
ViewModel
condiviso e l'API Fragment
Result. L'opzione consigliata dipende dal caso d'uso. Per condividere
i dati permanenti con le API personalizzate, utilizza un ViewModel
. Per
un risultato una tantum con dati che possono essere inseriti in una
Bundle
, utilizza l'API Fragment
Result.
Le seguenti sezioni mostrano come utilizzare ViewModel
e l'API Fragment
Result per comunicare tra frammenti e attività.
Condividere dati utilizzando un ViewModel
ViewModel
è la scelta ideale quando devi condividere dati tra più frammenti o tra frammenti e la relativa attività host.
ViewModel
oggetti archiviano e gestiscono i dati dell'interfaccia utente. Per maggiori informazioni su ViewModel
, consulta la
panoramica di ViewModel.
Condividi dati con l'attività dell'organizzatore
In alcuni casi, potrebbe essere necessario condividere i dati tra i frammenti e la relativa attività host. Ad esempio, potresti voler attivare/disattivare un componente dell'interfaccia utente globale in base a un'interazione all'interno di un frammento.
Considera quanto segue 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; } }
In questo esempio, i dati archiviati sono aggregati in una classe MutableLiveData
.
LiveData
è una classe di titolari di dati
osservabile con consapevolezza del ciclo di vita. MutableLiveData
consente di modificare il suo valore. Per maggiori informazioni su LiveData
, consulta la
panoramica di LiveData.
Sia il frammento sia l'attività host possono recuperare un'istanza condivisa di ViewModel
con ambito attività passando l'attività al costruttore ViewModelProvider
. L'elemento ViewModelProvider
gestisce la creazione di un'istanza di ViewModel
o il recupero, se esiste già. Entrambi i componenti possono osservare
e modificare questi dati.
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); }); } }
Condividi dati tra frammenti
Due o più frammenti nella stessa attività spesso devono comunicare tra loro. Ad esempio, immagina un frammento che mostra un elenco e un altro che consente all'utente di applicare vari filtri all'elenco. L'implementazione di questo caso non è banale senza i frammenti che comunicano direttamente, ma non sono più autonomi. Inoltre, entrambi i frammenti devono gestire lo scenario in cui l'altro frammento non è ancora stato creato o non è visibile.
Questi frammenti possono condividere un ViewModel
utilizzando l'ambito dell'attività per gestire questa comunicazione. Se condividi il ViewModel
in questo modo, i frammenti non devono essere a conoscenza l'uno dell'altro e l'attività non deve fare nulla per facilitare la comunicazione.
L'esempio seguente mostra come due frammenti possono utilizzare un elemento ViewModel
condiviso per comunicare:
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); } }
Entrambi i frammenti utilizzano la propria attività host come ambito per
ViewModelProvider
. Poiché i frammenti utilizzano lo stesso ambito, ricevono la stessa istanza di ViewModel
, che consente loro di comunicare tra loro.
Condividere dati tra un frammento principale e uno secondario
Quando lavori con i frammenti figlio, il frammento padre e i relativi frammenti figlio potrebbero dover condividere dati tra loro. Per condividere dati tra questi frammenti, utilizza il frammento padre come ambito ViewModel
, come mostrato nell'esempio seguente:
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); ... } }
Imposta l'ambito di un ViewModel sul grafico di navigazione
Se utilizzi la libreria di navigazione, puoi anche limitare un ViewModel
al ciclo di vita di NavBackStackEntry
di una destinazione. Ad esempio, ViewModel
può essere limitato all'ambito NavBackStackEntry
per 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. } } }
Per ulteriori informazioni su come definire l'ambito di un elemento ViewModel
in un NavBackStackEntry
, consulta
Interagire in modo programmatico con il componente Navigazione.
Ottenere risultati utilizzando l'API Fragment Result
In alcuni casi, potresti voler passare un valore una tantum tra due frammenti o tra un frammento e la sua attività host. Ad esempio, potresti avere un frammento che legge i codici QR, in modo da ritrasmettere i dati a un frammento precedente.
Nella versione Fragment 1.3.0 e successive,
ogni elemento FragmentManager
implementa
FragmentResultOwner
.
Ciò significa che un FragmentManager
può fungere da archivio centrale per i risultati relativi ai frammenti. Questa modifica consente ai componenti di comunicare tra loro impostando i risultati dei frammenti e monitorandoli, senza richiedere che i componenti abbiano riferimenti diretti l'uno all'altro.
Trasmetti risultati tra frammenti
Per ritrasmettere i dati al frammento A dal frammento B, imposta prima un listener di risultati sul frammento A, il frammento che riceve il risultato. Chiama setFragmentResultListener()
sul frammento A FragmentManager
, come mostrato nell'esempio seguente:
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. } }); }
Nel frammento B, il frammento che produce il risultato, imposta il risultato sullo stesso FragmentManager
utilizzando lo stesso requestKey
. A tale scopo, utilizza l'API setFragmentResult()
:
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); } });
Il frammento A riceve il risultato ed esegue il callback del listener una volta che il frammento è STARTED
.
Per una determinata chiave puoi avere un solo listener e un solo risultato. Se chiami setFragmentResult()
più di una volta per la stessa chiave e se il listener non è STARTED
, il sistema sostituisce eventuali risultati in attesa con il risultato aggiornato.
Se imposti un risultato senza un listener corrispondente per riceverlo, il risultato viene archiviato in FragmentManager
finché non imposti un listener con la stessa chiave. Quando un listener riceve un risultato e attiva il callback onFragmentResult()
, il risultato viene cancellato. Questo comportamento ha due implicazioni principali:
- I frammenti nello stack posteriore non ricevono risultati finché non vengono
stati scaricati e sono
STARTED
. - Se un frammento che ascolta un risultato è
STARTED
quando il risultato è impostato, il callback del listener si attiva immediatamente.
Risultati del frammento di test
Utilizza FragmentScenario
per testare le chiamate a setFragmentResult()
e setFragmentResultListener()
.
Crea uno scenario per il frammento sottoposto a test utilizzando launchFragmentInContainer
o launchFragment
, quindi chiama manualmente il metodo che non viene testato.
Per testare setFragmentResultListener()
, crea uno scenario con il
frammento che effettua la chiamata a setFragmentResultListener()
. Dopodiché
chiama direttamente setFragmentResult()
e verifica il risultato:
@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")
}
}
}
Per testare setFragmentResult()
, crea uno scenario con il frammento che effettua la chiamata a setFragmentResult()
. Dopodiché chiama direttamente setFragmentResultListener()
e verifica il risultato:
@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))
}
}
}
Passa i risultati tra frammenti padre e figlio
Per passare un risultato da un frammento figlio a un elemento padre,
utilizza getChildFragmentManager()
dal frammento principale anziché
getParentFragmentManager()
quando chiami 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. } }); }
Il frammento figlio imposta il risultato sul relativo FragmentManager
. Il padre riceve quindi il risultato una volta che il frammento è 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); } });
Ricevi i risultati nell'attività dell'host
Per ricevere un risultato relativo ai frammenti nell'attività host, imposta un listener di risultati nel gestore di frammenti utilizzando 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. } }); } }