Разделяй и властвуй: Navigation Component в многомодульном проекте

В этой статье вы узнаете, как можно организовать графы отдельных модулей / фич / user story, централизовать их, построить прямую навигацию между ними и присыпать сверху Safe Args плагином.

Вы сейчас в третьей части большого материала про Navigation Component в многомодульном проекте. Если не поняли ни единого слова выше, то призываю сначала ознакомиться с тем:

  • Что за зверь этот Navigation Component.

  • Как работает плагин Safe Args и что он делает.

Ну а если вы уже знакомы с этой библиотекой, то для вас есть приятный бонус в следующей статье — подход к организации iOS-like multistack-навигации.

Сначала посмотрим, как выглядит разбиение проекта на модули у нас в компании, в которой я работаю (magora-systems.com):

  1. :app — основной модуль и точка входа в приложение. Он должен знать обо всех модулях, участвующих в приложении.

  2. :core-модуль содержит в себе все базовые вещи: базовые классы, модели, entity, DTO, extension-ы и пр. 

  3. Утилитарные модули служат для инкапсуляции функционала основных компонентов приложения. Например, работа с сетью, БД или той же навигацией.

  4. Feature-модули заключают в себе работу определенной фичи / user story, будь то флоу или экран.

Что ж, давайте натянем сову на глобус сделаем навигацию с подключенным Safe Args плагином.

Если пользоваться одним единственным графом, то будет так:

Выглядит запутанно и неочевидно. Чтобы не допустить подобного, нужно совершить ряд действий:

  1. Организовать графы для каждого feature-модуля.

  2. Выделить отдельный Top-level граф.

  3. Сделать удобные переходы между графами модулей.

  4. Решить, где хранить Top-level граф.

Теперь подробнее о каждом.

Организовать графы для каждого feature-модуля

Чтобы не было проблем с навигацией между destination-ами внутри одного модуля, сделаем отдельные графы под каждый feature-модуль и там обозначим все конечные точки и связи между ними. При таком подходе навигация фичи полностью инкапсулируется внутри модуля, а Safe Args классы, принадлежащие соответствующему графу, генерируются только тут.

Выделить отдельный Top-level граф

Главный граф — это тот, который является стартовой точкой навигации приложения и заключает в себе все необходимые графы, между которыми происходит навигация.

Выглядит не так эффектно, зато эффективно.

Сделать удобные переходы между графами модулей

А для того, чтобы можно было навигироваться между графами различных фич, нужно подвести к каждому global action.

Решить, где хранить Top-level граф 

Тут есть несколько вариантов, у каждого есть свои плюсы и минусы:

  1. Базовый модуль (:core)

О нем знает большинство модулей, но не все. Он не знает ни об одном модуле, поэтому не сможет увидеть графы. Стоит заметить, что приложение скомпилится и будет работать несмотря на ошибки Lint-a, при мерже ресурсов все равно всё сливается воедино.

При изменении графа надо будет пересобирать проект, а так как от него зависит много модулей, то их тоже надо будет пересобрать.

  1. Главный модуль (:app)

+ Знает об абсолютно всех модулях.

О нем не знает ни один модуль.

Safe args сгенерирует global action-ы в недоступном для feature-модулей месте, поэтому мы не сможем ходить между графами.

Результаты не то чтобы очень радужные, но у нас есть целый один плюс в пользу :app-модуля, так что оставим Top-level граф в нем и займемся минусами этого подхода.Для этого нам понадобится…

Минус: о нем не знает ни один модуль

Решение: сделать отдельный модуль (:navigation), о котором будут знать абсолютно все модули, которые будут хоть как-то взаимодействовать с навигацией.

Добавить в него все id глобальных action-ов. Таким образом generated-файлы поймут, с чем работают, и будут иметь доступ к id каждого глобального перехода.

<item name="actionglobalnavsignin" type="id"/>
<item name="actionglobalnavsignup" type="id"/>
<item name="actionglobalnavhome" type="id"/>
<item name="actionglobalnavuserslist" type="id"/>
<item name="actionglobalnavuserdetails" type="id"/>
<item name="actionglobalnavon¨C11Cglobal¨C12Csettings" type="id"/>
<item name="action¨C13Cto_faq" type="id"/>

Минус: сгенерированные Directions и Args лежат в :app модуле 

Safe args сгенерирует global action-ы в недоступном для feature-модулей месте, поэтому мы не сможем ходить между графами.

Решение: перенести и доработать generated-файлы. Тут немного сложнее и придется запачкать руки о билд-скрипты. Generated-классы находятся в build-папке того модуля, где находится граф (сейчас это :app), а использовать его в :navigation-модуле неудобно. Поэтому воспользуемся костылем небольшой хитростью: во время билда дождемся конца работы таски generateSafeArgs, перекинем все созданные файлы в модуль навигации и, так как Args- и Directions-классы используют R файл модуля :app, добавим импорт нашего модуля навигации.

ext {
   navigationArgsPath = '/build/generated/source/navigation-args'
   appNavigation = "${project(':app).projectDir.path}$navigationArgsPath"
   navigationPath = "${project(':navigation').projectDir.path}$navigationArgsPath"
   navigationPackage = “com.example.navigation”
}

tasks.whenTaskAdded { task ->
   if (task.name.contains('generateSafeArgs')) {
       task.doLast {
           fileTree(appNavigation)
                   .filter { it.isFile() && it.name.contains("Directions") }
                   .forEach { file ->
                       if (file.exists()) {
                           def lines = file.readLines()
                           lines = lines.plus(2, "import $navigationPackage.R")
                           file.text = lines.join("n")
                       }
                   }
       }
       move(file("$appNavigation"), file("$navigationPath"))
   }
}

В итоге

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

Помимо всего прочего мы решили проблему с созданными плагином файлами — теперь ребилд в разные флейворы и типы не будет нам докучать, как бы это было с простым добавлением sourceSet-s в модуль навигации.

На этой победе я останавливаться не стал и решил посмотреть, что еще можно с этим сделать. Для этого как никогда кстати подошел проект, в котором заказчик хотел приложение с нижним меню и чтобы каждая вкладка сохраняла свое состояние при уходе с неё. Именно о таком решении финальная часть моей истории про iOS-like multistack-навигацию.

Let’s block ads! (Why?)

Read More

Recent Posts

VK купила 40% билетной платформы Intickets.ru

VK объявляет о приобретении 40% компании Intickets.ru (Интикетс). Это облачный сервис для контроля и управления продажей билетов на мероприятия. Сумма…

1 день ago

OpenAI готовится запустить поисковую систему на базе ChatGPT

OpenAI готовится запустить собственную поисковую систему на базе ChatGPT. Информацию об этом публикуют западные издания. Ожидается, что новый поисковик может…

1 день ago

Роскомнадзор рекомендовал хостинг-провайдерам ограничить сбор данных с сайтов для иностранных ботов

Центр управления связью общего пользования (ЦМУ ССОП) Роскомнадзора рекомендовал компаниям из реестра провайдеров ограничить доступ поисковых ботов к информации на российских сайтах.…

2 дня ago

Apple возобновила переговоры с OpenAI и Google для интеграции ИИ в iPhone

Apple возобновила переговоры с OpenAI о возможности внедрения ИИ-технологий в iOS 18, на основе данной операционной системы будут работать новые…

1 неделя ago

Российская «дочка» Google подготовила 23 иска к крупнейшим игрокам рекламного рынка

Конкурсный управляющий российской «дочки» Google подготовил 23 иска к участникам рекламного рынка. Общая сумма исков составляет 16 млрд рублей –…

1 неделя ago

Google завершил обновление основного алгоритма March 2024 Core Update

Google завершил обновление основного алгоритма March 2024 Core Update. Раскатка обновлений была завершена 19 апреля, но сообщил об этом поисковик…

1 неделя ago