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

Комментарии 19

Я бы не стал говорить, что всё прям ужасно. Если у вас на одном экране пара фрагментов и нужно взаимодействие, и именно эта комбинация этих фрагментов только один раз встречается в приложении, то зачем придумывать себе проблему и потом же решать её? Я понимаю, что это может ухудшать переиспользуемость, но что если она и не нужна в конкретном месте..

Поправил, спасибо. Погорячился с формулировкой. Последний раз приходилось разбираться в такой мешанине из shared view model…
а чем не устраивает вариант типа EventBus?
EventBus желательно использовать только если все приложение имеет какое то состояние и все приложение может его слушать и при этом лучше подписываться на бродкасты приложения.
Размазывать синглтон типа отто по всему приложению, потом оттираться замучаетесь.

Для 2 отдельных компонентов то сервис с подпиской и лучше, это их личная зависимость.
Каждый знает, что вот зависит от такого сервиса, вполне тестируемо, подменяемо.
EventBus слишком общее решение, которое добавляет только неявных зависимостей в код. Есть печальный опыт работы с ним, когда событий действительно много. А свой сервис позволит реализовать определенную логику взаимодействия, а не только отправлять сообщения.
Согласен с тем, что Shared ViewModel — не идеальное решение. Всё взаимодействие на нем завязывать не стоит. Но оно вполне применимо, например, в каких-нибудь пошаговых визардах. Это скорее полезный инструмент для определенного круга ситуаций.

Но и предложенный Вами сервис Shared ViewModel полностью не заменяет. Это уже отдаленно напоминает redux или mvi.
Остальное тоже не идеально. Опишу подробнее:

Мы строим ViewModel для конкретной View.
Не обязательно. В идеале ViewModel не должна знать о View, которая ее использует. Так мы разделяем логику (ViewModel) и механику (View) работы презентационного слоя. Таким образом, можно переиспользовать ViewModel на нескольких экранах схожего назначения. Например, можно использовать один и тот же тип ViewModel как в мобильной версии, так и в версии для Android TV.

Мы не можем просто взять такой фрагмент и добавить в ViewPager, например
И не нужно. Этим и распределением параметров должен заниматься наследник FragmentStateAdapter (или FragmentStatePagerAdapter). В идеале, по мере пролистывания, фрагменты должны создаваться и уничтожаться чтобы не мусорить в памяти. Решение этого вопроса сложнее, чем «просто добавить», и аргументы вполне подходят как часть решения.

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

ViewModel запрашивает параметры у сервиса по типу класса параметров.
И это порождает проблемы с уникальностью. Если у Вас в стэке несколько экземпляров одного и того-же фрагмента или фрагментов, которые используют один и тот-же тип параметров — после восстановления стейта Вы можете получить стопку одинаковых экранов.
Пример — экран профиля пользователя с параметром userId. Вы смотрите на профиль 3, в который попали из друзей профиля 2, в который попали так же из профиля 1. И в любой момент хотите вернуться к предыдущему. Да, в случае сервиса это решаемо, но в аргументах это всё уже решено.

Если объем передаваемых данных свыше 500 кб., то приложение может аварийно завершиться
Потому не стоит хранить в аргументах и saved state контент. Здесь надо хранить только параметры, и, в этом случае, лимит исчерпать довольно сложно.

Простите за большое количество текста. В паре строк эти вещи не опишешь.
Простите за большое количество текста.

Отличная дискуссия, спасибо!

Не обязательно. В идеале ViewModel не должна знать о View, которая ее использует. Так мы разделяем логику (ViewModel) и механику (View) работы презентационного слоя. Таким образом, можно переиспользовать ViewModel на нескольких экранах схожего назначения. Например, можно использовать один и тот же тип ViewModel как в мобильной версии, так и в версии для Android TV.


ViewModel ничего и не знает о View. ViewModel лишь содержит ниточки, через которые она взаимодействует с View. Механика View может быть любой. У нас в проекте ViewModel отлично работает в мобильной версии и АТВ. В некоторых случаях реализация сервисов общая, в некоторых — у каждого своя (DI).

И не нужно. Этим и распределением параметров должен заниматься наследник FragmentStateAdapter (или FragmentStatePagerAdapter).


Предпочитаю логику отделять от Android. Задача FragmentStatePagerAdapter лишь дать нужный фрагмент по требованию пользователя. А что этот фрагмент отобразит, известно логике.

И это порождает проблемы с уникальностью.


Такие же проблемы могут быть и у Safty args. После восстановления state параметры и не нужны, все уже живет во ViewModel. А если есть цепочки, как с примером о профилях, то эту breadcrumb логику я бы тоже перенес в сервис. Это немного сложнее, конечно.

Потому не стоит хранить в аргументах и saved state контент.

Безусловно! Но бывает соблазн быстренько что-то показать, например, в диалоге через Navigation Component.

В качестве резюме могу сказать, что я пытаюсь максимально абстрагироваться от Android, делая акцент на логике. Это помогает в тестировании и переходу к новым инструментам. Например, переход на Navigation Component получился почти безболезненным. Возможно, это также поможет мне, когда мы захотим попробовать Kotlin multiplatform.

Добрый день, почти получилось, только смерть процесса приложения забили учитывать.

Предпочитаю логику отделять от Android.
В данном конкретном случае адаптер, скорее, часть механики View. А вот дочерние фрагменты уже подключены к логике. В этом кейсе как раз и выгодно использовать Shared ViewModel — родительский фрагмент ее порождает, дочерние (из ViewPager) подключаются. При этом ничто Вам не мешает в дочерних порождать собственные ViewModel для, например, поставки данных.
Так или иначе, всю логику за пределы Android Вы не сможете вынести, как бы Вам не хотелось.
Такие же проблемы могут быть и у Safty args
Не могу представить себе такого кейса. Safe Args — просто обертка над аргументами. А один инстанс аргументов приходится на один инстанс фрагмента.
то эту breadcrumb логику я бы тоже перенес в сервис
Как говорится, «у любой задачи есть минимум 3 решения». Можно и по-Вашему, а можно взять готовое решение — Navigation Component + Safe Args, что обойдется дешевле во всех смыслах.
что я пытаюсь максимально абстрагироваться от Android, делая акцент на логике. Это помогает в тестировании и переходу к новым инструментам.
Ну вот для этого пакеты AndroidX поставляются с зависимостями для тестирования. Navigation Component не исключение.
А что будет с параметрами которые вьюмодель достает из стека если она будет пересоздана после смерти процесса? Или тут начинается возня с сохранением состояния?
С процессами всегда возни много. В момент сохранения состояния сервис параметров сбрасывает свои данные на persistance storage, потом достает, если надо. Здесь, конечно, простой push/pop не подойдет.

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

Что думаете насчет нового способа передачи данных между фрагментами FragmentResultOwner?
Решит ли это проблему?
Ну как бы взаимодействие между фрагментами — это не только отправка аргументов и получение результата. Часто нужно и во время работы фрагмента что-то дергать или получать в других фрагментах.

Такую штуку и не видел даже, в альфе ещё, вроде. Функционала недостаточно пока ещё, можно только передать результат, чем-то напоминает onActivityResult. Вот интересно, зачем в Гугл такую штуку добавили, ведь здесь тоже можно использовать SharedViewModel… Возможно, там тоже понимают проблему, и вскоре мы увидим новый arch component. Хорошо бы...

Возврат результата из фрагмента часто используется, например, в DialogFragment'ах. Они могут быть вызваны из разных экранов и поэтому не могут быть привязаны к какой-то одной shared ViewModel'и. Приходилось изворачиваться с getActivity/getParentFragment + кастинг к интерфейсу. А с Navigation Component и это не прокатывает, приходилось использовать костыли с nav back stack.

Несколько категоричное мнение, если честно. Фрагменты — это необязательно разные экраны в приложении. Если несколько фрагментов не представляют собой автономные единицы, то вполне можно сделать одну ViewModel. Да и сама она не должна знать о том, один там фрагмент или несколько. Это особенности отображения, которые еще и могут отличаться от устройства к устройству, в зависимости от конфигурации.

интересно было бы посмотреть на код
В свободное время я работаю над простым framework'ом на основе своих идей. Хороший пример будет.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории