Не обязательно. В идеале 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.
EventBus слишком общее решение, которое добавляет только неявных зависимостей в код. Есть печальный опыт работы с ним, когда событий действительно много. А свой сервис позволит реализовать определенную логику взаимодействия, а не только отправлять сообщения.
Переиспользование и разбросанное тестирование. UseCase можно использовать в любом месте, а не только во ViewModel. У меня в проекте они подключаются в сервисах, ресиверах, да где угодно. И везде используются сценарии, которые уже хорошо отлажены. Плюс, когда я написал сценарий, я сразу могу написать для тесты и проверить, что все правильно. И тесты логики находятся в одном месте вместе с реализацией.
Может, цвет не самый лучший пример получения каких-то данных путем расчета в логике приложения. Это не цвет из дизайна, конечно же. Например, цвет назначается для пользователя с определенным уровнем доступа. Т.е. этот параметр обладает логикой. Ваш пример с 2-я экранами: я бы сделал общий UseCase, который получает необходимые данные. И два UseCase для каждого экрана, каждый из которых включает предыдущий общий UseCase. Т.к. каждый UseCase получает данные из общего (DRY) и формирует ответ согласно своей логике, заданной в User Story.
Нет, логика ничего не знает о представлении. В идеале андроид можно заменить на что угодно, например, на веб. Это архитектура хорошо работает на ксамарин, где у меня есть 4 разных UI с общей логикой. Сценарий возвращает модель согласно User Story или фичи, которую дают менеджеры. Например: нужно отобразить список файлов и список участников, для которых файл доступен для редактирования, отсортированный по некоторому признаку. Логика обратится к нужным репозиториям, отфильтрует и вернет готовый список в виде простой модели, в которой есть только то, что нужно по этому сценарию. Логика ничего не знает о UI. UI принимает модель от логики (ui ссылается на модель логики), UI должен знать, что в этой модели хранится и как ее отобразить, а не наоборот. Может показаться, что логика подстраивается под UI, на самом деле и логика, и UI подстраиваются под ваши User Stories.
Да, в простом случае можно сразу биндить click. Но тут не совсем понятно, как передавать параметры, например, если View фрагмента состоит из нескольких layout, и одному из них надо запустить команду с параметрами другого layout. Также целесообразно использовать DataBinding везде, например, в сложных списках, в общем, BindingAdapters всегда вылезают. Был у меня проект, где дизайнеры такого выдумали, что в некоторых случаях сбиндить не удалось (может, не хватило знаний и умений), и в коде стали использоваться оба подхода. Ещё мне не нравится код в xml, какой-то возврат к истокам, сначала все бились над разделением кода и дизайна, теперь в дизайн добавляем логику, модели данных. Это, конечно, субъективно и навеяно опытом работы с ASP.NET.
В Android я пришел из C#, после WPF DataBinding на андроид не воспринимается. Но, в общем, DataBinding отлично подходит в этой архитектуре. Я просто не считаю его проще и удобнее. Описание данных в xml, адаптеры…
Мы используем Flow в репозиториях. Есть задачи, где стримы и трансформации удобны? но от маппинга это не освобождает. API всегда маппится и живет своей жизнью (однажды это мне очень помогло). Сценарии тоже выдают, так сказать, PresentationViewModel, внутри которых спрятано много логики. Можно LiveData заменить на Flow, если угодно. Но мне кажется, что LiveData во ViewModel удобнее и проще. Их цель просто передать состояние для View.
1. Не вижу проблемы, вызываем Dispose, операция просто отменяется
2. Тогда вы заменяете LiveData на DataBinding, а не LiveData на RX. В общем, такой вариант тоже рабочий, никто с этим не спорит.
Нет, смотрите: если грубо, сделайте во ViewModel вызов RX, внутри subscribe, когда RX успешно вернула результат своего запроса, данные выведите в LiveData. А LiveData уже сам оповестит своих подписчиков, если такие есть, т.е. где-то в фрагменте есть подписка на эту LiveData. И с ЖЦ проблем нет. Например, такой кейс: RX возвращает данные в момент переворота девайса, View уже уничтожена, если бы вы напрямую из RX обновляли View, то будет ошибка. А так, с LiveData, все пройдет как надо, переворот девайса завершится, View будет создана заново, и View обновит свое состояние из LiveData. LiveData и RX работают вместе.
Я пишу большую статью об архитектуре и дизайне Android – приложения, где собраны многие инструменты: ViewModel, LiveData, RX и т.д. Там я отвечу на этот вопрос подробно. Но, если коротко, то RX используется, например, внутри UseCases или провайдеров для асинхронной работы, маппинга и прочих прелестей, которые дает RX. Но когда данные выводятся пользователю, то данные прослаиваются через LiveData. И именно эта прослойка позволила избежать множества ошибок, связанных с жизненным циклом. Поэтому статья про события на основе LiveData, если нужно опять же работать в контексте жизненного цикла. А других независимых способов передачи событий полно: RX, какой-нибудь EventBus, что угодно.
В общем, можно вызвать самому dispose. Но LiveData и сделана для того, чтобы нам забыть про ручную обработку жизненного цикла. Кстати, не всегда dispose решает некоторые тонкие моменты. Например, если использовать LiveData, то после OnStop, данные придут в LiveData, и при следующем OnResume наш observer сработает и мы увидим сообщение. RX и LiveData решают разные задачи, на мой взгляд.
Потому что нужна привязка к LifecycleOwner. Нужно получать сообщения, когда LifecycleOwner активен. RX может прислать событие уже после того, как LifecycleOwner будет уничтожен. У LifecycleOwner много нюансов. Когда появились LiveData, жить с фрагментами и активити стало заметно легче.
Отличная дискуссия, спасибо!
ViewModel ничего и не знает о View. ViewModel лишь содержит ниточки, через которые она взаимодействует с View. Механика View может быть любой. У нас в проекте ViewModel отлично работает в мобильной версии и АТВ. В некоторых случаях реализация сервисов общая, в некоторых — у каждого своя (DI).
Предпочитаю логику отделять от Android. Задача FragmentStatePagerAdapter лишь дать нужный фрагмент по требованию пользователя. А что этот фрагмент отобразит, известно логике.
Такие же проблемы могут быть и у Safty args. После восстановления state параметры и не нужны, все уже живет во ViewModel. А если есть цепочки, как с примером о профилях, то эту breadcrumb логику я бы тоже перенес в сервис. Это немного сложнее, конечно.
Безусловно! Но бывает соблазн быстренько что-то показать, например, в диалоге через Navigation Component.
В качестве резюме могу сказать, что я пытаюсь максимально абстрагироваться от Android, делая акцент на логике. Это помогает в тестировании и переходу к новым инструментам. Например, переход на Navigation Component получился почти безболезненным. Возможно, это также поможет мне, когда мы захотим попробовать Kotlin multiplatform.
2. Тогда вы заменяете LiveData на DataBinding, а не LiveData на RX. В общем, такой вариант тоже рабочий, никто с этим не спорит.