Хабр, привет!

Задача организации взаимодействия между фрагментами встречается очень часто. На первый взгляд, ShareViewModel отлично подходит для этого. Мы создаем ViewModel с owner = наша activity, в которой отображаются наши фрагменты, и получаем эту ViewModel внутри каждого фрагмента. Т.к. владелец ViewModel — активити, то фрагменты получают один и тот же экземпляр ViewModel, что и позволяет им обмениваться данными, вызывать методы и т.д. Вот ссылка из документации.

На рисунке ниже представлена схема взаимодействия 3-х фрагментов.

image

Т.е. что мы делаем: в каждом фрагменте мы достаем SharedViewModel тех фрагментов, с которыми нам нужно взаимодействовать…

И это не самое лучшее решение, на мой взгляд. Потому что:

  1. Это полностью разрушает паттерн *VM. Мы строим ViewModel для конкретной View. ViewModel содержит разные LiveData, нужные этой View, необходимые методы и т.д. ViewModel не должна использоваться где-то ещё, кроме этой View.
  2. Каждая такая SharedViewModel — это лишняя зависимость для фрагмента. Нам нужно понимать, что в этой SharedViewModel происходит, что там нужно вызвать, чтобы второй фрагмент правильно отработал. И мы попросту можем что-то сломать.
  3. Нам нужно добавить во фрагмент все необходимые SharedViewModel, на которые этот фрагмент влияет или от которых зависит. И если в будущем появится еще фрагмент, с которым тоже надо взаимодействовать, то придется добавить в каждый фрагмент новую связь.

SharedViewModel можно использовать только тогда, когда есть несколько полностью одинаковых фрагментов с одинаковыми данными. В моей практике такого сценария еще не было. Может быть два одинаковых фрагмента, но состояние ViewModel каждого отличается, SharedViewModel для них недопустимо.

SharedViewModel приносит кучу проблем. Начинаешь путаться, все обрастает зависимостями, код усложняется. Гораздо проще работать, когда Fragment+ViewModel — это черный ящик.

Какое решение можно предложить?

Простой сервис, с которым каждый фрагмент взаимодействует по-своему.

image

Этот сервис должен передаваться внутрь каждой ViewModel. Благодаря DI и scope (Dagger, Koin, любые другие) это можно сделать легко и просто. Если сервис должен жить в контексте Activity, создайте его в Scope этой Activity и передайте каждой ViewModel.

Смотрите, что произошло: мы убрали все SharedViewModel из фрагментов. Каждый фрагмент теперь ничего не знает о других фрагментах. Если мы захотим добавить новый фрагмент в эту схему, то нам надо будет только связать viewModel D с нашим сервисом и все. Если фрагмент должен реагировать на изменение некоторого свойства сервиса — просто подписываемся на него. Нам больше не нужно разбираться в чужих ViewModels. И этот сервис будет гораздо проще любой ViewModel, разобраться в сервисе будет легче.

Может показаться, что на одном экране взаимодействующих фрагментов не так много. На телефоне — возможно. На планшете — уже нельзя так утверждать. А FragmentContainerView помогает использовать фрагменты в любых лейаутах, что очень удобно, если приложении много общих компонентов с жизненным циклом.

Бонус: как я создаю фрагменты?


Вот так: SomeFragment()

А что с параметрами?

С появлением ViewModel я не использую параметры. Я ничего не передаю во фрагмент через bundle (Android Studio до сих пор предлагает создавать фрагмент с аргументами и создавать фрагмент через static метод), я не использую Safety NavArgs для Navigation Component.

Все эти вещи делают фрагмент обусловленным. Мы не можем просто взять такой фрагмент и добавить в ViewPager, например. Надо думать, как передать параметры. Или другой пример: вы создали фрагмент с параметрами, работаете в нем, данные поменялись, фрагмент уже показывает что-то другое. Поменяли конфигурацию устройства (перевернули), фрагмент создался заново с устаревшими параметрами, в то время как ViewModel уже содержит другие данные. И начинается актуализация параметров и т.д.

Я это все убрал во ViewModel. Я использую такой же сервис, через который передаю параметры. Он работает наподобие Safety NavArgs. ViewModel запрашивает параметры у сервиса по типу класса параметров. После успешного извлечения параметра, сервис их удаляет (работает как Push/Pop).

Это сразу решает проблему смены конфигурации: фрагмент создался снова, а ViewModel уже с актуальными данными, все что нужно, это связать их со View.

Есть также проблема при передачи параметров через bundle — это превышение допустимого объема. Если объем передаваемых данных свыше 500 кб., то приложение может аварийно завершиться из-за возникшего исключения внутри Binder.java. Это привело к созданию разных библиотек, которые передают параметры через SD, что не очень быстро по сравнению с использованием RAM.

В общем, инструменты классные, все гибко, но нужно быть осторожным.

На этом все, спасибо за внимание!