[Разработка мобильных приложений, Разработка под Android] Получаем результат правильно (Часть 2). Fragment Result API

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
25-Май-2021 22:30


Мы продолжаем рассказ о новинках библиотеки Jetpack, призванных упростить обмен данными между компонентами Android приложения. Первая часть была посвящена передаче данных из Activity и новому Api Activity Result.  На этот раз посмотрим, какое решение Google предлагает для Fragment. Ввиду популярности паттерна “Single Activity” работа с фрагментами представляет большой практический интерес для многих Android-разработчиков.“Как передать данные между двумя фрагментами?” - частый вопрос на собеседованиях. Ответить на него можно по-разному: создание общей ViewModel, имплементация интерфейса в Activity, использование targetFragment и другие способы. С появлением Fragment Result Api в этот список добавился простой способ передачи небольшого объема информации из одного фрагмента в другой. Например, возвращение результата какого-либо пользовательского сценария. Мы разберем, как применять новый Api на практике, но сначала немного теории.ТеорияНачиная с версии 1.3.0-alpha04, FragmentManager реализует интерфейс FragmentResultOwner. Это означает, что FragmentManger является диспетчером для результатов, которые отправляют фрагменты. Благодаря этому фрагменты могут обмениваться информацией, не имея прямых ссылок друг на друга.Таким образом, всё взаимодействие происходит через FragmentManager:
  • Если фрагмент ожидает получить некоторые данные от другого фрагмента, он должен зарегистрировать слушатель во FragmentManger с помощью метода setFragmentResultListener()
  • Если фрагменту необходимо вернуть результат другому фрагменту, он передает FragmentManger объект Bundle, содержащий информацию. Для этого вызывается метод setFragmentResult()
  • Чтобы FragmentManger знал, как соотнести Bundle с нужным слушателем, необходимо указывать строковый ключ при регистрации слушателя и при передаче результата. 
Упрощенно данную схему можно представить так:
FragmentB передает данные в FragmentA . FragmentManager выполняет роль диспетчераДостоинством Fragment Result Api является lifecycle-безопасность - результат передается во фрагмент, только когда тот достиг состояния STARTED, но еще не находится в состоянии DESTROYED. “Под капотом” FragmentManger хранит все зарегистрированные слушатели и все отправленные результаты в потокобезопасных реализациях Map:
  • Map<String, Bundle> для результатов, отправленных фрагментами 
  • Map<String, LifecycleAwareResultListener> для зарегистрированных слушателей
Когда фрагмент регистрирует FragmentResultListener, FragmentManager добавляет его в Map, а при уничтожении фрагмента, слушатель удаляется из Map. Для того, чтобы учитывать жизненный цикл фрагмента, FragmentResultListener оборачивается в LifecycleAwareResultListener.При отправке результата, FragmentManager ищет зарегистрированный с тем же ключом слушатель и передает ему результат. Если слушатель не найден, то результат сохраняется в Map в ожидании дальнейшего использования.А теперь практика.ПрактикаВ качестве примера возьмем следующий кейс: ProductsFragment содержит список товаров, которые можно сортировать по различным критериям, а SortFragment позволяет указать нужную сортировку. Информация о выбранной сортировке будет передаваться с помощью Fragment Result Api. 
Так выглядит итоговая реализация, которую можно найти по ссылке нижеВ коде все выглядит довольно просто. Чтобы наладить передачу результата, необходимо выполнить всего два шага. Шаг 1В ProductsFragment, который ожидает получить результат, мы должны зарегистрировать слушатель с помощью FragmentManager. Для этого воспользуемся экстеншен-функцией setFragmentResultListener из fragment-ktx, которая принимает строковый ключ и слушатель, обрабатывающий результат. Регистрацию слушателя можно произвести в колбеке onCreate():
override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setFragmentResultListener("request_key") { key, bundle ->
        val selectedSort = bundle.getParcelable<Sort>("extra_key")
        // применение полученной сортировки
   }
}
Шаг 2 Когда SortFragment будет готов отправить результат, вызывается метод setFragmentResult, в который передается тот же строковый ключ и заполненный объект Bundle. 
applyButton.setOnClickListener {
   setFragmentResult(
      "request_key",
       bundleOf("extra_key" to getSelectedSort())
   )
}
Вот и всё, что требуется для передачи результата с помощью Fragment Result Api. Важно Хотя Api довольно прост, стоит разобрать некоторые нюансы его работы, связанные с правильным выбором FragmentManager и жизненным циклом фрагментов.Выбор FragmentManagerFragmentManager выполняет основную работу в передаче результата от одного фрагмента к другому. Но каждому фрагменту доступен выбор из нескольких вариантов: parentFragmentManager, childFragmentManager и FragmentManager у активити-хоста. Разберемся, в каких случаях стоит выбирать тот или иной FragmentManager.Сначала представим так называемую master-detail конфигурацию. Активити содержит два фрагмента, FragmentA и FragmentB, между которыми требуется передать результат.
Активити является хостом для FragmentA и FragmentBВ таком случае передавать результат между фрагментами может FragmentManager активити-хоста, т.к. доступ к нему имеют оба фрагмента. Получить данный FragmentManager можно путем вызова requireActivity().supportFragmentManager либо parentFragmentManager.Следующая ситуация характерна, например, для открытия DialogFragment или в случае, если FragmentA размещает внутри себя FragmentC. 
FragmentA является хостом для FragmentСПри таком сценарии, передать результат из FragmentС в FragmentA можно двумя способами:
  • Через FragmentManager активити с помощью requireActivity().supportFragmentManager
  • Через дочерний FragmentManager у FragmentA. Чтобы получить на него ссылку, FragmentA должен обращаться к childFragmentManager, а FragmentС к parentFragmentManager.
Особенности LifeсycleКак уже сказано, Fragment Result Api обеспечивает lifecycle-безопасность - результат доставляется, только если фрагмент находится на экране. Рассмотрим несколько примеров. Представим стандартный случай - фрагмент подписывается в колбеке onCreate, затем переходит в состояние STARTED, и как только другой фрагмент передает во FragmentManager результат, фрагмент-подписчик его получает.
Фрагмент получит лишь bundle3, так как он был отправлен последнимЕсли еще до перехода фрагмента в состояние STARTED, во FragmentManager было передано несколько результатов, то фрагмент получит лишь последний из них (так как FragmentManager хранит результаты в Map<String, Bundle>, то каждый последующий перезаписывает предыдущий).
Автоматическая отписка фрагментов происходит при достижении
состояния DESTROYED
Если после закрытия вновь открыть этот же фрагмент, ему будет доставлен тот результат, который он “не успел” получить.
Если фрагмент фрагмент-подписчик был закрыт до отправки результата,
он получит его при повторном открытии.В том случае, когда фрагмент не закрыт окончательно, а лишь находится в бэкстеке (в таком случае он в состоянии CREATED), то результат будет доставлен, как только пользователь вернется к этому фрагменту.
Сценарий при нахождении фрагмента в бэкстеке в момент
передачи результатаВсе рассмотренные ситуации объединяет то, что фрагмент подписывался по уникальному строковому ключу. Но что если сразу несколько подписчиков будут использовать один и тот же ключ? Напомним, что FragmentManager сохраняет информацию о подписках в Map<String, LifecycleAwareListener>, следовательно не может содержать несколько записей с одним и тем же ключом. Именно поэтому результат будет доставлен в тот фрагмент, который зарегистрировал слушатель последним.
Результат получает только последний подписчикЗаключение Подводя итог, отметим достоинства нового способа передачи результата между фрагментами: 
  • Fragment Result Api является стабильным, можно не бояться использовать его в продакшене. Тем, кто использует targetFrament особенно стоит присмотреться, ведь targetFrament стал Deprecated.
  • Api прост в использовании и не требует написания большого количества кода
  • Учитывает жизненный цикл фрагментов - при получении результата, можно сразу работать со view фрагмента
  • Позволяет пережить изменение конфигурации и даже смерть процесса (FragmentManager умеет сохранять данные о переданных результатах в Parcelable)
Но присутствуют и недостатки: 
  • необходимо следить за уникальностью строковых ключей, а также выбирать правильное место для их хранения
  • так как результат передается в Bundle, отсутствует его типизация. При неаккуратном обращении, можно получить ClassCastException. 
В целом, Fragment Result Api оставляет положительное впечатление, и точно стоит того, чтобы его опробовать, а наглядный пример можно найти по ссылке
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_razrabotka_mobilnyh_prilozhenij (Разработка мобильных приложений), #_razrabotka_pod_android (Разработка под Android), #_android, #_fragment, #_google, #_mobile_development, #_kotlin, #_blog_kompanii_elegion (
Блог компании e-Legion
)
, #_razrabotka_mobilnyh_prilozhenij (
Разработка мобильных приложений
)
, #_razrabotka_pod_android (
Разработка под Android
)
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 24-Ноя 05:13
Часовой пояс: UTC + 5