The right way to get a result. Part 2. Fragment Result API
We continue the story about the Jetpack library’s latest updates that are designed to simplify data exchange between different parts of Android apps. The first part was about data transfer between Activities via the new Activity Result Api.
This time, we’ll cover the Fragment-based solution offered by Google. Due to the popularity of the Single Activity pattern, working with fragments is of great practical interest to many Android developers.
“How to transfer data between two Fragments?” — is a frequent job interview question. It can have various answers: creating a common ViewModel, implementing the interface in the Activity, using the targetFragment or some other methods.
As the Fragment Result Api was released, a simple way of transferring a small amount of information from one fragment to another has been added to this list. For example, returning the result of some user scenarios. We’ll describe how to bring the new Api to practice, but first, a bit of theory.
Theory
Since version 1.3.0-alpha04, FragmentManager implements the FragmentResultOwner interface. This means that FragmentManger is a dispatcher for the results sent by the fragments. Because of this, fragments can exchange information without direct lisnks to each other.
All the interaction occurs through the FragmentManager:
- If a fragment expects to receive some data from another fragment, it must register a listener in the FragmentManger using the setFragmentResultListener() method.
- If a fragment needs to send a result to another fragment, it passes a Bundle object with the information to FragmentManger. The setFragmentResult() method is called to do this.
- In order for FragmentManger to know how to relate the Bundle with the relevant listener, you must specify a string key when registering the listener and when transmitting the result.
In a simplified way, the result-passing scheme can be represented like this:
New Api has one big advantage and it is its lifecycle-safety — the result is passed to the fragment only when it reaches the STARTED state, but not yet in the DESTROYED state.
Under the hood FragmentManger stores all registered listeners and all sent results in thread-safe Map implementations:
- Map<String, Bundle> for the results sent by fragments
- Map<String, LifecycleAwareResultListener> for registered listeners
When a fragment registers a FragmentResultListener, the FragmentManager adds it to the Map, and when the fragment is destroyed, the listener is removed from the Map. In order to consider the fragment’s lifecycle, the FragmentResultListener is wrapped in the LifecycleAwareResultListener.
When sending a result, the FragmentManager searches for a listener registered with the same key and passes the result to it. In case the listener is not found, the result is stored in the Map, expecting its further use.
Let’s check out how the new mechanism works in practice.
Practice
Take a look at the following example: ProductsFragment contains a list of products that can be sorted by various criteria, and SortFragment allows to specify the desired sorting criterion. The information about the selected sorting will be transmitted by the Fragment Result Api.
Step 1
In the ProductsFragment, which expects to receive the result, we must register a listener with the FragmentManager. To do this, we will use the extension-function setFragmentResultListener from fragment-ktx, which accepts a string key and a listener processing the result.
Registration of the listener can be done in the onCreate() callback:
Step 2
When SortFragment is ready to send the result, the setFragmentResult method is called with the same string key and the filled Bundle object.
That’s it. That’s all that was required to send a result using Fragment Result Api.
Important
Hot Api is quite simple, though it is crucial to understand certain nuances related to the correct choice of FragmentManager and the lifecycle.
FragmentManager choice
The FragmentManager does most of the work in passing the result from one fragment to another. But each fragment has several options to choose from: parentFragmentManager, childFragmentManager, and activity host FragmentManager. Let’s find out which cases presume the choice of one or another FragmentManager.
First, we get into the so-called master-detail configuration. The Activity contains two fragments, FragmentA and FragmentB, between which we want to pass some data.
In this case, the activity’s FragmentManager can transfer the result between fragments, because both fragments have access to it. You can get this FragmentManager by calling requireActivity().supportFragmentManager or parentFragmentManager.
Next situation is typical, for example, when opening a DialogFragment or when FragmentA places FragmentC within itself.
In this scenario, there are two ways to transfer the result from FragmentC to FragmentA:
- Through activity’s FragmentManager with requireActivity().supportFragmentManager
- Through the FragmentA’s child FragmentManager. To get a reference to it, FragmentA must refer to childFragmentManager and FragmentC to parentFragmentManager.
Lifecycle nuances
As mentioned earlier, Fragment Result Api is lifecycle-safe— the result is delivered only if the fragment is on the screen. Let’s take a look at some examples.
Let’s introduce the standard case — the fragmentResultListener is set in the onCreate callback, then fragment reaches the STARTED state, and as soon as another fragment sends the result to the FragmentManager, the subscriber fragment receives it.
If before the fragment reached the STARTED state, the FragmentManager receivedseveral results, then the fragment will receive only the latest result (since the FragmentManager stores results in Map<String, Bundle>, each subsequent one overwrites the previous one).
Now imagine a situation with the fragment being closed before sending the result to the FragmentManager. In this case the subscription will close when reaching the DESTROYED state and the FragmentManager will not send the result to the fragment. But it will internally save result to the map.
If we reopen the same fragment after closing, it will get the result that it “didn’t have time” to receive.
Scenario of the fragment not being finally closed, sitting in the back stack (which means it is in the CREATED state), the result will be delivered as soon as the user returns to this fragment.
Scenario of the fragment not being finally closed, sitting in the back stack (which means it is in the CREATED state), the result will be delivered as soon as the user returns to this fragment.
Conclusion
To sum up, let’s note advantages of the new way of transferring the result between fragments:
- Fragment Result Api is stable, while the preceding targetFragment is deprecated.
- The Api is easy to use and it doesn’t require to write a lot of code
- It’s lifecycle-safe — when receiving a result, you can work with the fragment view
- FragmentManager knows how save data about the passed results in Parcelable, which allows to survive configuration changes and even process death
Yet, in my opinion there are some disadvantages:
- you need to keep string keys unique, and choose the correct place to store them
- since the result is sent to a Bundle, there is no typization. If you make a mistake, you can get a ClassCastException.
All in all, Fragment Result Api leaves a positive impression, so let’s try it out!
P.S. you can find a sample project in my github