We get the result right (Part 2). Fragment Result API

We continue the story about the novelties of the Jetpack library, designed to simplify the exchange of data between the components of an Android application. First part was devoted to transferring data from Activity and the new Api Activity Result.

This time, let’s see what Google’s solution is for Fragment. Due to the popularity of the Single Activity pattern, working with fragments is of great practical interest for many Android developers.

“How to transfer data between two chunks?” – a frequent question in interviews. You can answer it in different ways: creating a common ViewModel, implementing an interface in an Activity, using a targetFragment, and other ways.

With the advent of the Fragment Result Api, a simple way to transfer a small amount of information from one fragment to another has been added to this list. For example, returning the result of some custom script. We’ll cover how to put the new Api into practice, but first, a little theory.

Theory

Since version 1.3.0-alpha04, FragmentManager implements the FragmentResultOwner interface. This means that the FragmentManger is the dispatcher for the results that send the fragments. Thanks to this, fragments can exchange information without having direct links to each other.

Thus, all interaction takes place through the FragmentManager:

  • If a fragment expects to receive some data from another fragment, it must register a listener with the FragmentManger using the method setFragmentResultListener()

  • If a Fragment needs to return the result to another Fragment, it passes the FragmentManger a Bundle containing the information. For this, the method is called setFragmentResult()

  • In order for the FragmentManger to know how to associate the Bundle with the desired listener, you need to specify a string key when registering the listener and when passing the result.

Simplified, this scheme can be represented as follows:

FragmentB passes data to FragmentA.  FragmentManager acts as a dispatcher
FragmentB passes data to FragmentA. FragmentManager acts as a dispatcher

The advantage of the Fragment Result Api is lifecycle safety – the result is passed to the fragment only when it has reached the STARTED state, but is not yet in the DESTROYED state.

Under the hood, FragmentManger stores all registered listeners and all posted results in thread-safe Map implementations:

  • Map<String, Bundle> for results sent in chunks

  • 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 account for the lifecycle of a fragment, the FragmentResultListener is wrapped in a LifecycleAwareResultListener.

When sending the result, the FragmentManager looks for a listener registered with the same key and passes the result to it. If no listener is found, then the result is stored in a Map pending further use.

Now practice.

Practice

Let’s take the following case as an example: ProductsFragment contains a list of products that can be sorted according to various criteria, and SortFragment allows you to specify the desired sorting. The information about the selected sort will be transmitted using the Fragment Result Api.

This is how the final implementation looks like, which can be found at the link below.
This is how the final implementation looks like, which can be found at the link below.

Everything looks pretty simple in the code. To set up the transmission of the result, you need to complete only two steps.

Step 1

In the ProductsFragment that expects to receive the result, we must register the listener with the FragmentManager. To do this, we will use the extension function setFragmentResultListener of fragment-ktx, which takes a string key and a listener to process the result.

The registration of the listener can be done in the onCreate () callback:

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setFragmentResultListener("request_key") { key, bundle ->
        val selectedSort = bundle.getParcelable<Sort>("extra_key")
        // применение полученной сортировки
   }
}

Step 2

When the SortFragment is ready to send the result, the setFragmentResult method is called, passing in the same string key and the filled Bundle.

applyButton.setOnClickListener {
   setFragmentResult(
      "request_key",
       bundleOf("extra_key" to getSelectedSort())
   )
}

That’s all it takes to pass the result using the Fragment Result Api.

Important

Although the Api is quite simple, it is worth understanding some of the nuances of its work associated with the correct choice of FragmentManager and the lifecycle of fragments.

FragmentManager selection

The FragmentManager does the bulk of the work in passing the result from one fragment to another. But each fragment has a choice of several options: parentFragmentManager, childFragmentManager and FragmentManager from the activity host. Let’s figure out in what cases it is worth choosing one or another FragmentManager.

First, let’s present the so-called master-detail configuration. The activity contains two fragments, FragmentA and FragmentB, between which you want to transfer the result.

Activity is the host for FragmentA and FragmentB
Activity is the host for FragmentA and FragmentB

In this case, the FragmentManager of the host activity can transfer the result between the fragments, since both fragments have access to it. You can get this FragmentManager by calling requireActivity().supportFragmentManager or parentFragmentManager

The following situation is typical, for example, for opening a DialogFragment or if FragmentA places FragmentC inside itself.

FragmentA is the host for FragmentC
FragmentA is the host for FragmentC

In this scenario, pass the result from FragmentC to FragmentA can be done in two ways:

  • Through the FragmentManager, activate with requireActivity (). supportFragmentManager

  • Through the child FragmentManager of FragmentA. To get a link to it, FragmentA must refer to childFragmentManager, but FragmentC to parentFragmentManager.

Features of Lifeсycle

As already mentioned, the Fragment Result Api provides lifecycle safety – the result is delivered only if the fragment is on the screen. Let’s look at a few examples.

Let’s imagine a standard case – a fragment is subscribed in the onCreate callback, then goes into the STARTED state, and as soon as another fragment passes the result to the FragmentManager, the subscriber fragment receives it.

The fragment will only receive bundle3, since it was sent last
The fragment will only receive bundle3, since it was sent last

If even before the fragment went to the STARTED state, several results were passed to the FragmentManager, then the fragment will receive only the last of them (since the FragmentManager stores the results in Map , each subsequent one overwrites the previous one).

Fragments are automatically unsubscribed when the DESTROYED state is reached
Fragments are automatically unsubscribed when the DESTROYED state is reached

If, after closing, the same fragment is reopened, it will receive the result that it “did not have time” to receive.

If the snippet subscriber was closed before sending the result, it will receive it when it is reopened.
If the snippet subscriber was closed before sending the result, it will receive it when it is reopened.

In the case when the fragment is not closed completely, but is only in the backstack (in this case, it is in the CREATED state), the result will be delivered as soon as the user returns to this fragment.

Scenario when a fragment is in the backstack at the time of transferring the result
Scenario when a fragment is in the backstack at the time of transferring the result

All the considered situations are united by the fact that the fragment was signed using a unique string key. But what if several subscribers use the same key at once? Recall that the FragmentManager stores subscription information in Map , therefore it cannot contain multiple entries with the same key. That is why the result will be delivered to the fragment that registered the listener last.

Only the last subscriber gets the result
Only the last subscriber gets the result

Conclusion

Summing up, let’s note the merits of the new way of transferring the result between fragments:

  • Fragment Result Api is stable, so you shouldn’t be afraid to use it in production. Those who use targetFrament should take a closer look, because targetFrament has become Deprecated.

  • Api is easy to use and does not require writing a lot of code

  • Takes into account the life cycle of fragments – when receiving the result, you can immediately work with the view of the fragment

  • Allows you to survive the configuration change and even the death of the process (FragmentManager is able to save data about the transferred results in Parcelable)

But there are also disadvantages:

  • you need to keep track of the uniqueness of string keys, as well as choose the right place to store them

  • since the result is passed to the Bundle, there is no typing. If mishandled, you can get a ClassCastException.

Overall, the Fragment Result Api leaves a positive impression and is definitely worth trying it out, and a good example can be found here.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *