Communicate with fragments
Stay organized with collections
Save and categorize content based on your preferences.
To reuse fragments, build them as completely self-contained components that define their own layout and behavior. Once you define these reusable fragments, you can associate them with an activity and connect them with the application logic to realize the overall composite UI.
To properly react to user events and to share state information, you often need to have channels of communication between an activity and its fragments or between two or more fragments. To keep fragments self-contained, don't have fragments communicate directly with other fragments or with their host activity.
The Fragment library provides two options for communication: a shared
ViewModel and the Fragment
Result API. The recommended option depends on the use case. To share
persistent data with custom APIs, use a ViewModel. For
a one-time result with data that can be placed in a
Bundle, use the Fragment
Result API.
The following sections show you how to use ViewModel and the Fragment
Result API to communicate between your fragments and activities.
Share data using a ViewModel
ViewModel is an ideal choice when
you need to share data between
multiple fragments or between fragments and their host activity.
ViewModel objects store and
manage UI data. For more information about ViewModel, see
ViewModel overview.
Share data with the host activity
In some cases, you might need to share data between fragments and their host activity. For example, you might want to toggle a global UI component based on an interaction within a fragment.
Consider the following ItemViewModel:
Kotlin
classItemViewModel:ViewModel(){ privatevalmutableSelectedItem=MutableLiveData<Item>() valselectedItem:LiveData<Item>get()=mutableSelectedItem funselectItem(item:Item){ mutableSelectedItem.value=item } }
Java
publicclass ItemViewModelextendsViewModel{ privatefinalMutableLiveData<Item>selectedItem=newMutableLiveData<Item>(); publicvoidselectItem(Itemitem){ selectedItem.setValue(item); } publicLiveData<Item>getSelectedItem(){ returnselectedItem; } }
In this example, the stored data is wrapped in a
MutableLiveData class.
LiveData is a lifecycle-aware
observable data holder class. MutableLiveData lets its value be
changed. For more information about LiveData, see
LiveData overview.
Both your fragment and its host activity can retrieve a shared instance
of a ViewModel with activity scope by passing the activity into the
ViewModelProvider
constructor. The ViewModelProvider handles instantiating the ViewModel
or retrieving it if it already exists. Both components can observe and
modify this data.
Kotlin
classMainActivity:AppCompatActivity(){ // Using the viewModels() Kotlin property delegate from the activity-ktx // artifact to retrieve the ViewModel in the activity scope. privatevalviewModel:ItemViewModelbyviewModels() overridefunonCreate(savedInstanceState:Bundle?){ super.onCreate(savedInstanceState) viewModel.selectedItem.observe(this,Observer{item-> // Perform an action with the latest item data. }) } } classListFragment:Fragment(){ // Using the activityViewModels() Kotlin property delegate from the // fragment-ktx artifact to retrieve the ViewModel in the activity scope. privatevalviewModel:ItemViewModelbyactivityViewModels() // Called when the item is clicked. funonItemClicked(item:Item){ // Set a new item. viewModel.selectItem(item) } }
Java
publicclass MainActivityextendsAppCompatActivity{ privateItemViewModelviewModel; @Override publicvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); viewModel=newViewModelProvider(this).get(ItemViewModel.class); viewModel.getSelectedItem().observe(this,item->{ // Perform an action with the latest item data. }); } } publicclass ListFragmentextendsFragment{ privateItemViewModelviewModel; @Override publicvoidonViewCreated(@NonNullViewview,BundlesavedInstanceState){ super.onViewCreated(view,savedInstanceState); viewModel=newViewModelProvider(requireActivity()).get(ItemViewModel.class); ... items.setOnClickListener(item->{ // Set a new item. viewModel.select(item); }); } }
Share data between fragments
Two or more fragments in the same activity often need to communicate with each other. For example, imagine one fragment that displays a list and another that lets the user apply various filters to the list. Implementing this case isn't trivial without the fragments communicating directly, but then they are no longer self-contained. Additionally, both fragments must handle the scenario where the other fragment is not yet created or visible.
These fragments can share a ViewModel using their activity scope
to handle this communication. By sharing the ViewModel in this way,
the fragments don't need to know about each other, and the activity
doesn't need to do anything to facilitate the communication.
The following example shows how two fragments can use a shared
ViewModel to communicate:
Kotlin
classListViewModel:ViewModel(){ valfilters=MutableLiveData<Set<Filter>>() privatevaloriginalList:LiveData<List<Item>>()=... valfilteredList:LiveData<List<Item>>=... funaddFilter(filter:Filter){...} funremoveFilter(filter:Filter){...} } classListFragment:Fragment(){ // Using the activityViewModels() Kotlin property delegate from the // fragment-ktx artifact to retrieve the ViewModel in the activity scope. privatevalviewModel:ListViewModelbyactivityViewModels() overridefunonViewCreated(view:View,savedInstanceState:Bundle?){ viewModel.filteredList.observe(viewLifecycleOwner,Observer{list-> // Update the list UI. } } } classFilterFragment:Fragment(){ privatevalviewModel:ListViewModelbyactivityViewModels() overridefunonViewCreated(view:View,savedInstanceState:Bundle?){ viewModel.filters.observe(viewLifecycleOwner,Observer{set-> // Update the selected filters UI. } } funonFilterSelected(filter:Filter)=viewModel.addFilter(filter) funonFilterDeselected(filter:Filter)=viewModel.removeFilter(filter) }
Java
publicclass ListViewModelextendsViewModel{ privatefinalMutableLiveData<Set<Filter>>filters=newMutableLiveData<>(); privatefinalLiveData<List<Item>>originalList=...; privatefinalLiveData<List<Item>>filteredList=...; publicLiveData<List<Item>>getFilteredList(){ returnfilteredList; } publicLiveData<Set<Filter>>getFilters(){ returnfilters; } publicvoidaddFilter(Filterfilter){...} publicvoidremoveFilter(Filterfilter){...} } publicclass ListFragmentextendsFragment{ privateListViewModelviewModel; @Override publicvoidonViewCreated(@NonNullViewview,BundlesavedInstanceState){ super.onViewCreated(view,savedInstanceState); viewModel=newViewModelProvider(requireActivity()).get(ListViewModel.class); viewModel.getFilteredList().observe(getViewLifecycleOwner(),list->{ // Update the list UI. }); } } publicclass FilterFragmentextendsFragment{ privateListViewModelviewModel; @Override publicvoidonViewCreated(@NonNullViewview,BundlesavedInstanceState){ viewModel=newViewModelProvider(requireActivity()).get(ListViewModel.class); viewModel.getFilters().observe(getViewLifecycleOwner(),set->{ // Update the selected filters UI. }); } publicvoidonFilterSelected(Filterfilter){ viewModel.addFilter(filter); } publicvoidonFilterDeselected(Filterfilter){ viewModel.removeFilter(filter); } }
Both fragments use their host activity as the scope for the
ViewModelProvider. Because the fragments use the same scope, they receive
the same instance of the ViewModel, which enables them to communicate
back and forth.
Share data between a parent and child fragment
When working with child fragments, your parent fragment and its child
fragments might need to share data with each other. To share data between
these fragments, use the parent fragment as the ViewModel scope, as shown
in the following example:
Kotlin
classListFragment:Fragment(){ // Using the viewModels() Kotlin property delegate from the fragment-ktx // artifact to retrieve the ViewModel. privatevalviewModel:ListViewModelbyviewModels() overridefunonViewCreated(view:View,savedInstanceState:Bundle?){ viewModel.filteredList.observe(viewLifecycleOwner,Observer{list-> // Update the list UI. } } } classChildFragment:Fragment(){ // Using the viewModels() Kotlin property delegate from the fragment-ktx // artifact to retrieve the ViewModel using the parent fragment's scope privatevalviewModel:ListViewModelbyviewModels({requireParentFragment()}) ... }
Java
publicclass ListFragmentextendsFragment{ privateListViewModelviewModel; @Override publicvoidonViewCreated(@NonNullViewview,BundlesavedInstanceState){ viewModel=newViewModelProvider(this).get(ListViewModel.class); viewModel.getFilteredList().observe(getViewLifecycleOwner(),list->{ // Update the list UI. } } } publicclass ChildFragmentextendsFragment{ privateListViewModelviewModel; @Override publicvoidonViewCreated(@NonNullViewview,BundlesavedInstanceState){ viewModel=newViewModelProvider(requireParentFragment()).get(ListViewModel.class); ... } }
Scope a ViewModel to the Navigation Graph
If you're using the Navigation library, you can also
scope a ViewModel to the lifecycle of a destination's
NavBackStackEntry. For
example, the ViewModel can be scoped to the NavBackStackEntry
for the ListFragment:
Kotlin
classListFragment: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 privatevalviewModel:ListViewModelbynavGraphViewModels(R.id.list_fragment) overridefunonViewCreated(view:View,savedInstanceState:Bundle?){ viewModel.filteredList.observe(viewLifecycleOwner,Observer{item-> // Update the list UI. } } }
Java
publicclass ListFragmentextendsFragment{ privateListViewModelviewModel; @Override publicvoidonViewCreated(@NonNullViewview,BundlesavedInstanceState){ NavControllernavController=NavHostFragment.findNavController(this); NavBackStackEntrybackStackEntry=navController.getBackStackEntry(R.id.list_fragment) viewModel=newViewModelProvider(backStackEntry).get(ListViewModel.class); viewModel.getFilteredList().observe(getViewLifecycleOwner(),list->{ // Update the list UI. } } }
For more information about scoping a ViewModel to a NavBackStackEntry, see
Interact programmatically with the Navigation component.
Get results using the Fragment Result API
In some cases, you might want to pass a one-time value between two fragments or between a fragment and its host activity. For example, you might have a fragment that reads QR codes, passing the data back to a previous fragment.
In Fragment version 1.3.0 and higher,
each FragmentManager
implements
FragmentResultOwner.
This means that a FragmentManager can act as a central store for fragment
results. This change lets components communicate with each other by
setting fragment results and listening for those results, without requiring
those components to have direct references to each other.
Pass results between fragments
To pass data back to fragment A from fragment B, first set a result listener
on fragment A, the fragment that receives the result. Call
setFragmentResultListener()
on fragment A's FragmentManager, as shown in the following example:
Kotlin
overridefunonCreate(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. valresult=bundle.getString("bundleKey") // Do something with the result. } }
Java
@Override publicvoidonCreate(@NullableBundlesavedInstanceState){ super.onCreate(savedInstanceState); getParentFragmentManager().setFragmentResultListener("requestKey",this,newFragmentResultListener(){ @Override publicvoidonFragmentResult(@NonNullStringrequestKey,@NonNullBundlebundle){ // We use a String here, but any type that can be put in a Bundle is supported. Stringresult=bundle.getString("bundleKey"); // Do something with the result. } }); }
FragmentManager.In fragment B, the fragment producing the result, set the result
on the same FragmentManager by using the same requestKey. You can do
so by using the
setFragmentResult()
API:
Kotlin
button.setOnClickListener{ valresult="result" // Use the Kotlin extension in the fragment-ktx artifact. setFragmentResult("requestKey",bundleOf("bundleKey"toresult)) }
Java
button.setOnClickListener(newView.OnClickListener(){ @Override publicvoidonClick(Viewv){ Bundleresult=newBundle(); result.putString("bundleKey","result"); getParentFragmentManager().setFragmentResult("requestKey",result); } });
Fragment A then receives the result and executes the listener callback
once the fragment is
STARTED.
You can have only a single listener and result for a given key. If you call
setFragmentResult() more than once for the same key, and if the listener
is not STARTED, the system replaces any pending results with your updated
result.
If you set a result without a corresponding listener to receive it,
the result is stored in the FragmentManager until you set a listener with
the same key. Once a listener receives a result and fires the
onFragmentResult() callback, the result is cleared. This behavior has
two major implications:
- Fragments on the back stack do not receive results until they have been
popped and are
STARTED. - If a fragment listening for a result is
STARTEDwhen the result is set, the listener's callback then fires immediately.
Test fragment results
Use
FragmentScenario
to test calls to setFragmentResult() and setFragmentResultListener().
Create a scenario for the fragment under test by using
launchFragmentInContainer
or
launchFragment,
and then manually call the method that isn't being tested.
To test setFragmentResultListener(), create a scenario with the
fragment that makes the call to setFragmentResultListener(). Next,
call setFragmentResult() directly, and verify the result:
@Test
funtestFragmentResultListener(){
valscenario=launchFragmentInContainer<ResultListenerFragment>()
scenario.onFragment{fragment->
valexpectedResult="result"
fragment.parentFragmentManager.setFragmentResult("requestKey",bundleOf("bundleKey"toexpectedResult))
assertThat(fragment.result).isEqualTo(expectedResult)
}
}
classResultListenerFragment:Fragment(){
varresult:String?=null
overridefunonCreate(savedInstanceState:Bundle?){
super.onCreate(savedInstanceState)
// Use the Kotlin extension in the fragment-ktx artifact.
setFragmentResultListener("requestKey"){requestKey,bundle->
result=bundle.getString("bundleKey")
}
}
}
To test setFragmentResult(), create a scenario with the fragment that makes the
call to setFragmentResult(). Next, call setFragmentResultListener()
directly, and verify the result:
@Test
funtestFragmentResult(){
valscenario=launchFragmentInContainer<ResultFragment>()
lateinitvaractualResult: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")
}
classResultFragment:Fragment(R.layout.fragment_result){
overridefunonViewCreated(view:View,savedInstanceState:Bundle?){
view.findViewById(R.id.result_button).setOnClickListener{
valresult="result"
// Use the Kotlin extension in the fragment-ktx artifact.
setFragmentResult("requestKey",bundleOf("bundleKey"toresult))
}
}
}
Pass results between parent and child fragments
To pass a result from a child fragment to a parent,
use getChildFragmentManager() from the parent fragment instead of
getParentFragmentManager() when calling setFragmentResultListener().
Kotlin
overridefunonCreate(savedInstanceState:Bundle?){ super.onCreate(savedInstanceState) // Set the listener on the child fragmentManager. childFragmentManager.setFragmentResultListener("requestKey"){key,bundle-> valresult=bundle.getString("bundleKey") // Do something with the result. } }
Java
@Override publicvoidonCreate(@NullableBundlesavedInstanceState){ super.onCreate(savedInstanceState); // Set the listener on the child fragmentManager. getChildFragmentManager() .setFragmentResultListener("requestKey",this,newFragmentResultListener(){ @Override publicvoidonFragmentResult(@NonNullStringrequestKey,@NonNullBundlebundle){ Stringresult=bundle.getString("bundleKey"); // Do something with the result. } }); }
FragmentManager to send a result to its parent.The child fragment sets the result on its FragmentManager. The parent
then receives the result once the fragment is STARTED:
Kotlin
button.setOnClickListener{ valresult="result" // Use the Kotlin extension in the fragment-ktx artifact. setFragmentResult("requestKey",bundleOf("bundleKey"toresult)) }
Java
button.setOnClickListener(newView.OnClickListener(){ @Override publicvoidonClick(Viewv){ Bundleresult=newBundle(); result.putString("bundleKey","result"); // The child fragment needs to still set the result on its parent fragment manager. getParentFragmentManager().setFragmentResult("requestKey",result); } });
Receive results in the host activity
To receive a fragment result in the host activity, set a result listener
on the fragment manager using
getSupportFragmentManager().
Kotlin
classMainActivity:AppCompatActivity(){ overridefunonCreate(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. valresult=bundle.getString("bundleKey") // Do something with the result. } } }
Java
class MainActivityextendsAppCompatActivity{ @Override publicvoidonCreate(@NullableBundlesavedInstanceState){ super.onCreate(savedInstanceState); getSupportFragmentManager().setFragmentResultListener("requestKey",this,newFragmentResultListener(){ @Override publicvoidonFragmentResult(@NonNullStringrequestKey,@NonNullBundlebundle){ // We use a String here, but any type that can be put in a Bundle is supported. Stringresult=bundle.getString("bundleKey"); // Do something with the result. } }); } }