The right way to get a result. Part 2. Fragment Result API

Leonid Belyakov
e-Legion
Published in
6 min readMay 31, 2021

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:

FragmentB sends data to FragmentA. FragmentManager acts as a dispatcher

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.

his is how the final implementation looks like, it can be found by the link

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.

Activity is the host for FragmentA and FragmentB

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.

FragmentA is a host to FragmentC

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.

Standard scenario

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

Only bundle3 will receive the fragment, since it was sent last

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.

The automatic fragment unsubscription occurs when the DESTROYED state

If we reopen the same fragment after closing, it will get the result that it “didn’t have time” to receive.

Scenario when we reopen the fragment after closing. The result will be delivered

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.

When yser returns to the fragment in the backstack, it recives the result if any

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.

If you subscribe at the same time, only the last subscriber will get the result

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

--

--