- The right way to get a result. Part 2. Fragment Result API
- Theory
- Practice
- Step 1
- Step 2
- Important
- FragmentManager choice
- Lifecycle nuances
- Conclusion
- Android Fragment Result Listener
- Как это работает?
- Как это выглядит в коде?
- Передача данных
- Получение данных
- Parent Fragment Manger
- Тестирование
- Передача данных
- Получение данных
- Вывод
- Android Fragment Result Listener
- Как это работает?
- Как это выглядит в коде?
- Передача данных
- Получение данных
- Parent Fragment Manger
- Тестирование
- Передача данных
- Получение данных
- Вывод
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 for the results sent by fragments
- Map 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 childFragmentManagerand 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 , 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!
Источник
Android Fragment Result Listener
В Android передача данных между фрагментами может осуществляться разными способами: передача через родительскую Activity, используя ViewModel или даже Fragments API. Fragment Target API с недавних пор получил статус Deprecated и вместо него Google рекомендует использовать Fragment result API.
Что такое Fragment result API? Это новый инструмент от Google который позволяет передавать данные между фрагментами по ключу. Для этого используется FragmentManager, который в свою очередь реализует интерфейс FragmentResultOwner. FragmentResultOwner выступает в качестве центрального хранилища для данных, которые мы передаем между фрагментами.
Как это работает?
Как упоминалось выше, наш FragmentManager реализует интерфейс FragmentResultOwner, который хранит в себе ConcurrentHashMap . Эта HashMap хранит наши Bundle-ы по строковому ключу. Как только один из фрагментов подписывается (или уже подписан) то он получает результат по тому самому ключу.
Что важно знать:
- Если какой-либо фрагмент подписывается на результат методом setResultFragmentListener() после того, как отправляющий фрагмент вызовет setFragmentResult() , то он немедленно получит результат
- Каждую связку “Key + Result (Bundle)“ фрагмент получает только 1 раз
- Фрагменты которые находятся в бек стеке получат результат только после того как перейдут в состояние STARTED
- После того как фрагмент перейдет в состояние DESTROYED мы больше не сможем подписываться на ResultListener
Как это выглядит в коде?
Передача данных
Для передачи данных в другой фрагмент нам необходимо вызвать метод:
В параметры метода мы кладем ключ, который и будет нашим идентификатором для получения данных и сам Bundle. Этот Bundle будет содержать в себе передаваемые данные.
Получение данных
Для получения данных через FragmentManager мы регистрируем наш FragmentResultListener и задаем ключ по которому мы будем получать данные. Тот самый ключ который мы указывали в методе FragmentManager.setFragmentResult()
Здесь мы видим 2 аргумента: key: String и bundle: Bundle.
Первый — это тот самый ключ, по которому мы передаем сюда данные. Второй — Bundle, в котором лежат переданные данные.
Parent Fragment Manger
Выбор FragmentManager-а для передачи данных между фрагментами зависит от принимающего фрагмента:
- Если оба фрагмента находятся в одном и том же FragmentManager (например оба фрагмента находятся в Activity), то мы должны использовать родительский FragmentManager, который хранит в себе Activity
- Если у нас один фрагмент вложен в другой фрагмент, то для передачи данных мы используем childFragmentManager (он же родительский фрагмент для принимающего фрагмента)
Важно понимать, что наш FragmentResultListener должен находиться в общем для двух фрагментов FragmentManager-е.
Тестирование
Для тестирования отправки/получения данных через FragmentResultListener, мы можем использовать FragmentScenario API, который предоставляет нам все преимущества тестирования фрагментов в изоляции.
Передача данных
Как мы можем протестировать, что наш фрагмент корректно отправляет данные через родительский FragmentManager? Для этого нам необходимо внутри теста отправить результат и проверить, что наш FragmentResultListener получил корректные данные:
Получение данных
Для проверки корректности получения данных мы можем симулировать отправку данных, используя родительский FragmentManager. Если в отправляющем фрагменте корректно установлен FragmentResultListener мы должны получить корректные данные проверяя сам листенер или последствие их получения.
Вывод
В данный момент FragmentResultListener находится в альфе, а это значит что возможно еще будут изменения со стороны Google. Но уже сейчас видно, что это достаточно крутой инструмент, для передачи данных между фрагментами, не создавая дополнительных интерфейсов и классов. Единственным нюансом остается, пожалуй то, что не совсем понятно, как и где лучше хранить ключи где, но это не кажется таким уж большим минусом.
Для того чтоб получить возможность использовать FragmentResultListener нам нужно подключить в зависимостях версию фрагментов 1.3.0-alpha04 или новее:
Источник
Android Fragment Result Listener
В Android передача данных между фрагментами может осуществляться разными способами: передача через родительскую Activity, используя ViewModel или даже Fragments API. Fragment Target API с недавних пор получил статус Deprecated и вместо него Google рекомендует использовать Fragment result API.
Что такое Fragment result API? Это новый инструмент от Google который позволяет передавать данные между фрагментами по ключу. Для этого используется FragmentManager, который в свою очередь реализует интерфейс FragmentResultOwner. FragmentResultOwner выступает в качестве центрального хранилища для данных, которые мы передаем между фрагментами.
Как это работает?
Как упоминалось выше, наш FragmentManager реализует интерфейс FragmentResultOwner, который хранит в себе ConcurrentHashMap . Эта HashMap хранит наши Bundle-ы по строковому ключу. Как только один из фрагментов подписывается (или уже подписан) то он получает результат по тому самому ключу.
Что важно знать:
- Если какой-либо фрагмент подписывается на результат методом setResultFragmentListener() после того, как отправляющий фрагмент вызовет setFragmentResult() , то он немедленно получит результат
- Каждую связку “Key + Result (Bundle)“ фрагмент получает только 1 раз
- Фрагменты которые находятся в бек стеке получат результат только после того как перейдут в состояние STARTED
- После того как фрагмент перейдет в состояние DESTROYED мы больше не сможем подписываться на ResultListener
Как это выглядит в коде?
Передача данных
Для передачи данных в другой фрагмент нам необходимо вызвать метод:
В параметры метода мы кладем ключ, который и будет нашим идентификатором для получения данных и сам Bundle. Этот Bundle будет содержать в себе передаваемые данные.
Получение данных
Для получения данных через FragmentManager мы регистрируем наш FragmentResultListener и задаем ключ по которому мы будем получать данные. Тот самый ключ который мы указывали в методе FragmentManager.setFragmentResult()
Здесь мы видим 2 аргумента: key: String и bundle: Bundle.
Первый — это тот самый ключ, по которому мы передаем сюда данные. Второй — Bundle, в котором лежат переданные данные.
Parent Fragment Manger
Выбор FragmentManager-а для передачи данных между фрагментами зависит от принимающего фрагмента:
- Если оба фрагмента находятся в одном и том же FragmentManager (например оба фрагмента находятся в Activity), то мы должны использовать родительский FragmentManager, который хранит в себе Activity
- Если у нас один фрагмент вложен в другой фрагмент, то для передачи данных мы используем childFragmentManager (он же родительский фрагмент для принимающего фрагмента)
Важно понимать, что наш FragmentResultListener должен находиться в общем для двух фрагментов FragmentManager-е.
Тестирование
Для тестирования отправки/получения данных через FragmentResultListener, мы можем использовать FragmentScenario API, который предоставляет нам все преимущества тестирования фрагментов в изоляции.
Передача данных
Как мы можем протестировать, что наш фрагмент корректно отправляет данные через родительский FragmentManager? Для этого нам необходимо внутри теста отправить результат и проверить, что наш FragmentResultListener получил корректные данные:
Получение данных
Для проверки корректности получения данных мы можем симулировать отправку данных, используя родительский FragmentManager. Если в отправляющем фрагменте корректно установлен FragmentResultListener мы должны получить корректные данные проверяя сам листенер или последствие их получения.
Вывод
В данный момент FragmentResultListener находится в альфе, а это значит что возможно еще будут изменения со стороны Google. Но уже сейчас видно, что это достаточно крутой инструмент, для передачи данных между фрагментами, не создавая дополнительных интерфейсов и классов. Единственным нюансом остается, пожалуй то, что не совсем понятно, как и где лучше хранить ключи где, но это не кажется таким уж большим минусом.
Для того чтоб получить возможность использовать FragmentResultListener нам нужно подключить в зависимостях версию фрагментов 1.3.0-alpha04 или новее:
Источник