Как стать автором
Обновить

Android Fragment Result Listener

Время на прочтение5 мин
Количество просмотров32K


В Android передача данных между фрагментами может осуществляться разными способами: передача через родительскую Activity, используя ViewModel или даже Fragments API. Fragment Target API с недавних пор получил статус Deprecated и вместо него Google рекомендует использовать Fragment result API.


Что такое Fragment result API? Это новый инструмент от Google который позволяет передавать данные между фрагментами по ключу. Для этого используется FragmentManager, который в свою очередь реализует интерфейс FragmentResultOwner. FragmentResultOwner выступает в качестве центрального хранилища для данных, которые мы передаем между фрагментами.


Как это работает?


image

Как упоминалось выше, наш FragmentManager реализует интерфейс FragmentResultOwner, который хранит в себе ConcurrentHashMap<String, Bundle>. Эта HashMap хранит наши Bundle-ы по строковому ключу. Как только один из фрагментов подписывается (или уже подписан) то он получает результат по тому самому ключу.


Что важно знать:


  • Если какой-либо фрагмент подписывается на результат методом setResultFragmentListener() после того, как отправляющий фрагмент вызовет setFragmentResult(), то он немедленно получит результат
  • Каждую связку “Key + Result (Bundle)“ фрагмент получает только 1 раз
  • Фрагменты которые находятся в бек стеке получат результат только после того как перейдут в состояние STARTED
  • После того как фрагмент перейдет в состояние DESTROYED мы больше не сможем подписываться на ResultListener

Как это выглядит в коде?


Передача данных


Для передачи данных в другой фрагмент нам необходимо вызвать метод:


FragmentManager.setFragmentResult(key: String, bundle: Bundle)

В параметры метода мы кладем ключ, который и будет нашим идентификатором для получения данных и сам Bundle. Этот Bundle будет содержать в себе передаваемые данные.


Kotlin


button.setOnClickListener {
    val result = "result"
    // Здесь мы можем использовать Kotlin экстеншен функцию из fragment-ktx
    setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}

Java


button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Bundle result = new Bundle();
        result.putString("bundleKey", "result");
        getParentFragmentManager().setFragmentResult("requestKey", result);
    }
});

Получение данных


Для получения данных через FragmentManager мы регистрируем наш FragmentResultListener и задаем ключ по которому мы будем получать данные. Тот самый ключ который мы указывали в методе FragmentManager.setFragmentResult()


Kotlin


override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
     // Здесь так же используется Kotlin экстеншен 
    setFragmentResultListener("requestKey") { key, bundle ->
        // Здесь можно передать любой тип, поддерживаемый Bundle-ом
        val result = bundle.getString("bundleKey")
    }
}

Java


@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getParentFragmentManager().setFragmentResultListener("key", this, new FragmentResultListener() {
        @Override
        public void onFragmentResult(@NonNull String key, @NonNull Bundle bundle) {
            String result = bundle.getString("bundleKey");
        }
    });
}

Здесь мы видим 2 аргумента: key: String и bundle: Bundle.
Первый — это тот самый ключ, по которому мы передаем сюда данные. Второй — Bundle, в котором лежат переданные данные.


Parent Fragment Manger


image

Выбор FragmentManager-а для передачи данных между фрагментами зависит от принимающего фрагмента:


  • Если оба фрагмента находятся в одном и том же FragmentManager (например оба фрагмента находятся в Activity), то мы должны использовать родительский FragmentManager, который хранит в себе Activity
  • Если у нас один фрагмент вложен в другой фрагмент, то для передачи данных мы используем childFragmentManager (он же родительский фрагмент для принимающего фрагмента)

Важно понимать, что наш FragmentResultListener должен находиться в общем для двух фрагментов FragmentManager-е.


Тестирование


Для тестирования отправки/получения данных через FragmentResultListener, мы можем использовать FragmentScenario API, который предоставляет нам все преимущества тестирования фрагментов в изоляции.


Передача данных


Как мы можем протестировать, что наш фрагмент корректно отправляет данные через родительский FragmentManager? Для этого нам необходимо внутри теста отправить результат и проверить, что наш FragmentResultListener получил корректные данные:


@Test
fun testFragmentResult() {
    val scenario = launchFragmentInContainer<ResultFragment>()
    lateinit var actualResult: String?
    scenario.onFragment { fragment ->
        fragment.parentFragmentManagager.setResultListener("requestKey") { key, bundle ->
            actualResult = bundle.getString("bundleKey")
        }
    }
    onView(withId(R.id.result_button)).perform(click())
    assertThat(actualResult).isEqualTo("result")
}

class ResultFragment : Fragment(R.layout.fragment_result) {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        view.findViewById(R.id.result_button).setOnClickListener {
            val result = "result"
            setResult("requestKey", bundleOf("bundleKey" to result))
        }
    }
}

Получение данных


Для проверки корректности получения данных мы можем симулировать отправку данных, используя родительский FragmentManager. Если в отправляющем фрагменте корректно установлен FragmentResultListener мы должны получить корректные данные проверяя сам листенер или последствие их получения.


@Test
fun testFragmentResultListener() {
    val scenario = launchFragmentInContainer<ResultListenerFragment>()
    scenario.onFragment { fragment ->
        val expectedResult = "result"
        fragment.parentFragmentManagager.setResult("requestKey", bundleOf("bundleKey" to expectedResult))
        assertThat(fragment.result).isEqualTo(expectedResult)
    }
}

class ResultListenerFragment : Fragment() {
    var result : String? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setResultListener("requestKey") { key, bundle ->
            result = bundle.getString("bundleKey")
        }
    }
}

Вывод


В данный момент FragmentResultListener находится в альфе, а это значит что возможно еще будут изменения со стороны Google. Но уже сейчас видно, что это достаточно крутой инструмент, для передачи данных между фрагментами, не создавая дополнительных интерфейсов и классов. Единственным нюансом остается, пожалуй то, что не совсем понятно, как и где лучше хранить ключи где, но это не кажется таким уж большим минусом.


Для того чтоб получить возможность использовать FragmentResultListener нам нужно подключить в зависимостях версию фрагментов 1.3.0-alpha04 или новее:


  • Версия для Java: androidx.fragment:fragment:1.3.0-alpha04
  • Версия для Kotlin: androidx.fragment:fragment-ktx:1.3.0-alpha04
  • Тесты: androidx.fragment:fragment-testing:1.3.0-alpha04
Теги:
Хабы:
Всего голосов 10: ↑10 и ↓0+10
Комментарии4

Публикации