All streams
Search
Write a publication
Pull to refresh
8
0
Pavel Sidyakin @foxspa

Android Developer

Send message

Оно так и должно быть. Сущности, управляющие View (presenter, controller, VM, и т.п.) не должны выходить за пределы своего модуля и app не должен о них знать. Чем меньше модули знают друг о друге - тем лучше. В целом, если что-то может быть internal, то оно и должно таким быть.

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

Здравствуйте!

ViewModel должна жить в модуле вместе со своим экраном и быть internal. Нет смысла куда-то выносить фабрики VM в другой модуль.

Создание VM будет аналогично созданию презентера в примере. Или в этом примере - созданию контроллера. Вы, возможно, захотите использовать в фрагменте "by viewModels()" с кастомной фабрикой для создания VM.

В общем, не усложняйте. Попробуйте сделать VM internal и создавать кастомной фабрикой. Пример не подскажу, к сожалению.

Доброе утро :)

Насколько я понимаю, у Вас такая ситуация: есть экран А, он отображается и где-то меняет состояние. Потом погибает и отображается экран Б, который должен поюзать это состояние.

Если ссылка на компонент, где живёт это состояние, закопана только в компонентах экранов, то да, состояние может потеряться (зависит от того, как быстро GC его грохнет).

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

Я бы рекомендовал ещё подумать насчёт базы на data уровне. Тогда всё просто: модифицируем базу и изменения доступны для всех фичей. Можно даже не базу, а просто сеттинги. Или даже просто в памяти хранить. Самое важное - на data уровне и закрыть имплементацию репозиторием. Я делал такой пример: https://github.com/PavelSidyakin/ProductList Там Like шарится между экранами.

Это действительно слэнг :)) Означает что-то типа "заберёт себе". В данном случае имеется в виду "будет держать у себя" ссылку. Английский аналог "hold".

Насчёт тестов с логикой. В примере выше - да, согласен, что не стоит так делать.

А что насчёт параметризованных тестов, когда параметр приходит извне - в функцию теста? Кажется, что тут нормально написать if (параметр) { ... } else { ... }. В этом случае из одной тестовой функции образуется несколько тестов - сколько значений может принять параметр.

Данную проблему можно решить с применением подхода, описанного здесь: habr.com/ru/post/536106
В данном случае, нужно чтобы у активити был свой компонент и к нему можно привязать сколько угодно других компонентов. Когда компонент активити умрёт, то и все привязанные к нему компоненты тоже освободятся (помрут, если не используются где-то ещё).
Случай с активити описан в указанной статье. Обратите внимание на особенности работы с компонентом активити.
Альтернативный способ склейки модулей описан тут:
habr.com/ru/post/536106
По-хорошему — нет, они не утилитарные.

Взять хоть нотификации. Для показа нужен контекст приложения. Что уже делает этот модуль не утилитарным. Ему нужно в зависимости предоставлять контекст, а лучше вообще интерфейс репозитория, чтобы не зависеть от андроидских классов (фича тогда будет кроссплатформенной).

Биллинг (повезло, что у вас его нет :)). Тут понятно — у модуля будет куча репозиториев для общения с бэком, магазинами и т.п. При этом фичи могут проверять, можно ли что-то показывать при данном лицензионном состоянии, можно ли запускать сервисы, отображать ли кнопки и т.п. Т.е. фича биллинга — она именно фича и от неё могут многие зависеть.

И даже модуль с набором экранов — от него тоже могут зависеть другие модули. Эти экраны-то ведь нужно запускать. И могут быть общие экраны, например, фрагмент камеры. От него могут зависеть несколько других фичей.

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

Чтобы фича А воспользовалась фичёй Б, нужно:

1. Чтобы у фичи А был свой компонент и ComponentHolder.

2. Фича А декларирует в своих зависимостях интерфейс(ы) из фичи Б.

3. При инициализации ComponentHolder'а фичи А, мы прописываем, что она зависит от фичи Б и отдаём ей нужные интерфейсы из фичи Б.

4. Когда компонент фичи А будет создан, автоматически создастся и компонент фичи Б.

5. Внутри фичи А мы можем использовать интерфейсы фичи Б, которые были продекларированы в зависимостях. В случае dagger мы можем эти интерфейсы инжектить как любые другие интерфейсы из компонента фичи А.

Конкретно в случае фрагмента можно выдать в API фабрику, которая будет создавать фрагмент. Это есть в примере: github.com/PavelSidyakin/WeatherForecast/tree/refactor_to_multimodule_structure/feature/weather_details/src/main/java/com/example/weather_details
Это было бы идеально, если бы все модули могли зависеть только от утилитарных модулей.
Но реально модули вполне могут зависеть от других «фичевых» модулей или core-модулей.
Например, фичевые модули, от которых, скорее всего, будет зависеть много других модулей: лицензирование (сюда же покупки, биллинг), аналитика/статистика, управление камерой, пуш-нотификации и многие другие. Они утилитарные? Нет! Хотя бы необходимость контекста уже делает модуль не утилитарным. А некоторым ещё и репозитории свои понадобятся.
Плюс есть core-модули, от которых зависит несколько фич.

В общем, такого не бывает, чтобы все модули зависели только от утилитарных.
По поводу инициализации «100500 статиков в Application.onCreate()».

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

Ещё, тут не идёт инициализация модулей, т.е. внутренние компоненты модулей не создаются сразу в Application.onCreate(). Здесь идёт инициализация Component Holder’ов, т.е. инициализация способа инициализации модуля. Потом каждый модуль (точнее, внутренний компонент модуля) инициализируется лениво – когда он кому-то понадобится. И уничтожается, когда он никому не нужен.

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

Пусть у нас есть модуль А, который зависит от интерфейсов модуля Б.

Чтобы модуль А себя проинициализировал, он должен проинициализировать модуль Б. А это значит, он будет знать о деталях имплементации модуля Б, и, что самое печальное, зависеть от модуля, который содержит имплементацию модуля Б.

Если в приложении используется подход с разделением модулей на API/Impl для ускорения сборки, то модуль А будет зависеть и от API модуля Б и от его Impl, что сводит на нет цель разбиения на API/Impl – при изменении Impl модуля Б, пересоберётся и модуль А.

Если же мы делегируем склейку кому-то другому – модулю, который знает обо всех, то получится, что модуль А знает только об API модуля Б. И изменение Impl модуля Б не приведёт к пересборке модуля А. На роль «модуля, который знает обо всех» вполне подходит модуль Application. Можно сделать ещё один модуль, который будет склеивать и тоже знать обо всех других модулях, но не очень ясен смысл делать ещё один всезнающий модуль.

В итоге этот подход с чётким выделением интерфейсов FeatureAPI, FeatureDependencies и внешней склейкой позволяет делать модули более чистыми. У модуля чёткое API и чёткие зависимости. И модулю без разницы как будут проставлены имплементации зависимостей. Модуль просто декларирует, что «мне нужно это», а задача простановки зависимостей (склейка) – это не его проблемы.
Спасибо за фидбэк!
По поводу «отключаемости комментированием в build.gradle».
Это уже похоже на архитектуру с плагинами, когда в приложение можно динамически добавлять какой-либо функционал. Это можно реализовать с помощью динамической загрузки jar-ника, через AIDL (тогда дополнительный функционал добавляется установкой ещё одной APK) или другим способом. Но такая архитектура довольно-таки сложна в реализации и создавать её нужно только если это реально нужно. В реальных проектах такое встречается редко.
В реальности, модули создаются не для быстрой отключаемости. О целях выделения в модули написано в статье Андрея, упомянутой в начале текущей статьи. Прочитайте, чтобы понять для чего это вообще нужно.
Что использовать — MVP или MVVM — дело вкуса и поставленной цели.
Хоть внутри MVP c Moxy и напоминает MVVM, но снаружи, т.е. для пользователя библиотеки, это чистый MVP.
Отсюда и все преимущества MVP — более тщательное покрытие unit-тестами поведения UI. С MVVM можно протестировать модель, но не как она используется в имплементации.
Например, в случае MVVM, в модели может быть метод типа fun getData(): Data. Мы можем протестировать unit-тестом что вернул этот метод, но не то, как его использует View.
В случае с MVP в presenter не может быть get-методов. View очень глупая и только presenter говорит View что делать, а значит можно тщательнее протестировать поведение UI unit-тестом presenter'а.
Так что если покрытие тестами — не важно, то можно использовать MVVM. Если важно — то лучше MVP.

Information

Rating
Does not participate
Location
München, Bayern, Германия
Date of birth
Registered
Activity