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

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

Отличная статья!

Хороший материал, будет полезен всем кто использует компоуз)

А как вам такой вариант реализации многомодульности:

Каждая такая фича (отдельный экран) ничего не знает о способе навигации, но знает куда ему надо.

И есть отдельный модуль, в котором описана структура приложения и всей навигации.


Здравствуйте! Как и другие варианты, имеет право на жизнь. Нам кажется, что такой вариант подходит для небольших приложений с малым количеством экранов. При большом количестве экранов такой подход может привести к раздуванию этого модуля; плюс все модули начнут знать про "приватные" экраны других модулей

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

Не понимаю только, как одни экраны узнают про другие, если использовать примерно такой шаблон:

fun SomeScreen ( navigateToA: (SomeParametr: String) -> Unit, ...) { }
(код могу только так вставить)

Да, наверное пока нет идеального решения, надеемся оно выработается со временем. Кажется, вынесение api навигации в супермодуль не сильно поможет понимать структуру навигации - просто в одном модуле будет много методов и сверху также не будет понятно, как выглядит граф переходов.

Не понял вторую часть вопроса (если это был вопрос), уточните пожалуйста

Не понимаю, почему один модуль (экран) начнет знать про другие модули (экраны) в моем видении такой навигации?

Супер-модуль навигации? Все же наверное это плохо будет. А если разбить на подмодули?

Но у вашего похода, так же плохо будет понятен граф переходов. Ведь вы вызываете метод перехода у конкретного UI элемента. На сложном экране будет проблемно искать эти вызовы. И у вас модуль экрана знает об Api другого экрана, что тоже усложнит понимание.
Все же отдельно-вынесенная навигация должна быть понятнее.

Сомневаюсь, что появится одно решения для этой проблемы. Сколько не гугли, а всех свое видение и использует его только автор.

"Не понимаю, почему один модуль (экран) начнет знать про другие модули (экраны) в моем видении такой навигации?" - я так не писал, видимо произошло недопонимание.

Да, в нашем подходе так же не будет виден граф. Боюсь, так как в compose навигация работает через ссылки, без дополнительных ухищрений граф переходов увидеть не получится.
Тут не оговорено, но на compose хорошо ложится MVI-паттерн. Мы его так же применяем, поэтому у нас вызов всех переходов навигации на экране производится в одном месте, в зависимости от action, который пришел от viewmodel-и; то есть внутри экрана вся навигация в одном месте. То есть этот вопрос решается понятной организацией кода внутри фичи.

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

Возможно. Надеюсь, Android сообщество со временем придет к общему решению :)

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

Модуль навигации должен состоять из таких вызовов:
composable(route = LibraryNavTarget.Chapters.route,
content = {
val item = nav.getStringElement(MangaItem) ?: ""
val viewModel = chaptersViewModel(item)
ChaptersScreen(viewModel, nav::navigateUp)
} )

А экраны так:
@Composable fun ChaptersScreen(
viewModel: ChaptersViewModel,
navigateUp: () -> Unit) { ...

Понял. Чтобы не было циклических зависимостей (модуль навигации же должен будет знать про модули с экранами, а модули с экранами должны использовать навигацию), придется модуль навигации разбивать на navigation-api и navigation-impl. Где navigation-api будет состоять из множества интерфейсов, а navigation-impl содержать код, который вы привели выше.

То есть этот почти тот-же вариант, что и предложили мы, только теперь все some-feature-api объединены в один navigation-api. Плюс тут действительно в том, что все destinations описаны в одном месте; ну и что модулей станет меньше. Граф переходов мы так же не сможем понять (кстати, а зачем?). Минус - в раздувающемся модуле и необходимости открыть наружу приватные экраны и вью-модели (пример выше можно немного доработать - вью-модели можно инициировать прямо к конструкторе экрана, так что минус снимается).
Как я ответил в первом комментарии, такой подход имеет место быть, но мы посчитали что он нам не подойдет из-за указаных минусов.

Напишите если я вас не правильно понял

В целом вы правильно поняли меня.

ViewModel'и можно и не выносить наружу, достаточно лишь передать данные внутрь экрана. Ведь viewmodel`и наврятли придется использовать сразу с несколькими экранами.

Не понял, что это за приватные экраны? Вы в одном модуле реализуете несколько экранов и навигацию между ними?

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

а зачем ремемберить табы, чем
val tabs = remember { BottomTabs.values() }
лучше чем просто
val tabs = BottomTabs.values()

?

Думаю, чтобы не создавался новый массив Array<BottomTabs> при каждой рекомпозиции, что в свою очередь приводило бы к ненужной рекомпозиции BottomBar

remember и правда запоминает значение между рекомпозиции, но не для того, чтобы разработчики костыли свои избыточные оптимизации (это во фреймворке как раз и так само работает), а для того, чтобы какое-нибудь изначальное значение, переданное аргументом функции, не перезаписывало изменённую внутри переменную

Удобный подход. Спасибо за статью!

Зарегистрируйтесь на Хабре, чтобы оставить комментарий