Per riutilizzare i frammenti, sviluppali come componenti completamente autonomi che definiscono il proprio layout e comportamento. Una volta definiti questi riutilizzabili, puoi associarli a un'attività e collegarli con la logica dell'applicazione per realizzare l'UI composita complessiva.
Per reagire correttamente agli eventi utente e condividere le informazioni sullo stato, Necessità di canali di comunicazione tra un'attività e la sua o tra due o più frammenti. Per mantenere i frammenti autonomi, non hanno frammenti che comunicano direttamente con altri frammenti o con l'attività di organizzatore.
La libreria Fragment
offre due opzioni di comunicazione: un file
ViewModel
e il frammento
API Result. L'opzione consigliata dipende dal caso d'uso. Per condividere
dati permanenti con API personalizzate, usa un valore ViewModel
. Per
un risultato una tantum con dati che possono essere inseriti in un
Bundle
, usa il frammento
API Result.
Le seguenti sezioni mostrano come utilizzare ViewModel
e il frammento
API Result per comunicare tra frammenti e attività.
Condividi dati utilizzando un ViewModel
ViewModel
è la scelta ideale quando
devi condividere i dati
più frammenti o tra frammenti e la loro attività host.
ViewModel
di oggetti archivia e
per gestire i dati dell'interfaccia utente. Per ulteriori informazioni su ViewModel
, vedi
Panoramica di ViewModel.
Condividi i dati con l'attività dell'organizzatore
In alcuni casi, potrebbe essere necessario condividere i dati tra frammenti l'attività di organizzatore. Ad esempio, potresti voler attivare/disattivare una UI globale sulla base di 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
MutableLiveData
.
LiveData
è sensibile al ciclo di vita
osservabile. MutableLiveData
consente al suo valore di essere
è cambiato. Per ulteriori informazioni su LiveData
, vedi
Panoramica di LiveData.
Sia il frammento che l'attività host possono recuperare un'istanza condivisa
di un ViewModel
con ambito attività trasferendo l'attività al
ViewModelProvider
come costruttore. ViewModelProvider
gestisce l'istanza di ViewModel
o recuperarlo se esiste già. Entrambi i componenti possono osservare
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); }); } }
Condividere dati tra frammenti
Due o più frammenti nella stessa attività spesso devono comunicare con tra loro. Ad esempio, immagina un frammento che mostra un elenco un'altra che consente all'utente di applicare vari filtri all'elenco. L'implementazione di questa richiesta non è banale senza i frammenti comunicano direttamente, ma non sono più indipendente. Inoltre, entrambi i frammenti devono gestire lo scenario in cui l'altro frammento non è ancora stato creato o visibile.
Questi frammenti possono condividere un ViewModel
utilizzando l'ambito dell'attività
per gestire questa comunicazione. Se condividi ViewModel
in questo modo,
i frammenti non hanno bisogno di 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
ViewModel
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 il
ViewModelProvider
. Poiché i frammenti usano lo stesso ambito, ricevono
la stessa istanza di ViewModel
, il che consente loro di comunicare
avanti e indietro.
Condividere dati tra un frammento padre e un frammento secondario
Quando lavori con i frammenti figlio, il frammento principale e il relativo file secondario
potrebbero dover condividere dati tra loro. Per condividere i dati tra
questi frammenti, utilizza il frammento padre come ambito ViewModel
, come mostrato
nel seguente esempio:
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); ... } }
Definizione dell'ambito di un ViewModel per il grafico di navigazione
Se utilizzi la Libreria di navigazione, puoi anche
l'ambito di un ViewModel
al ciclo di vita del ciclo di vita
NavBackStackEntry
. Per
Ad esempio, ViewModel
può limitare l'ambito a 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 sulla definizione dell'ambito di un ViewModel
in un NavBackStackEntry
, consulta
Interagisci in modo programmatico con il componente Navigazione.
Ricevi 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 una che legge i codici QR, ritrasmettendo i dati a un frammento precedente.
In Fragment 1.3.0 e versioni successive,
ogni FragmentManager
implements
FragmentResultOwner
.
Ciò significa che un FragmentManager
può fungere da archivio centrale per il frammento
che consentono di analizzare i dati
e visualizzare i risultati. Questa modifica consente ai componenti di comunicare tra loro
impostare risultati con frammenti e rimanere in ascolto di questi risultati, senza richiedere
che questi componenti abbiano riferimenti diretti l'uno all'altro.
Passa i risultati tra i 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 FragmentManager
del frammento A, 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
. Cosa puoi fare
quindi utilizzando
setFragmentResult()
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); } });
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 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 i risultati
o il risultato finale.
Se imposti un risultato senza un listener corrispondente per riceverlo,
il risultato viene memorizzato in FragmentManager
finché non imposti un listener con
la stessa chiave. Quando un listener riceve un risultato e attiva
Callback onFragmentResult()
, il risultato è stato cancellato. Questo comportamento ha
due implicazioni principali:
- I frammenti nella pila posteriore non ricevono risultati finché non
sono scoppiati e sono
STARTED
. - Se il frammento in ascolto di un risultato è
STARTED
quando il risultato è impostato, il callback del listener si attiva immediatamente.
Risultati dei frammenti di test
Utilizza le funzionalità di
FragmentScenario
per testare le chiamate a setFragmentResult()
e setFragmentResultListener()
.
Crea uno scenario per il frammento sottoposto a test utilizzando
launchFragmentInContainer
o
launchFragment
,
e quindi richiamare manualmente il metodo che non viene testato.
Per testare setFragmentResultListener()
, crea uno scenario con
frammento che effettua la chiamata a setFragmentResultListener()
. Poi,
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 rende la
chiamata a setFragmentResult()
. Successivamente, chiama setFragmentResultListener()
direttamente 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()
del frammento padre anziché
getParentFragmentManager()
durante la chiamata a 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
. Partner principale
riceve 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 risultati nell'attività organizzatore
Per ricevere un risultato di frammento nell'attività host, imposta un listener di risultati
sul gestore di frammenti
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. } }); } }