Как организовать навигацию между фичами в многомодульном Android приложении
Что проверяют
Понимаете ли вы главную цель многомодульности: фичи не знают друг о друге, иначе появятся циклические зависимости и пропадёт выигрыш в скорости сборки. Вопрос в том, как экран одной фичи открывает экран другой, не импортируя её. Следом спрашивают про передачу аргументов и про диплинки.
Как отвечать
- правило зависимостей: модули
:feature:*зависят только от:core:*, а собирает всё:app. Крупные фичи делят на:feature:profile:api(маршруты, контракты) и:feature:profile:impl, соседи видят толькоapi; - Navigation Compose с типобезопасными маршрутами: начиная с Navigation 2.8 маршрут это
@Serializableкласс или объект. Фича публикует расширенияNavGraphBuilder.profileScreen()иNavController.navigateToProfile(id), аNavHostв:appих собирает. Так устроен пример Now in Android; - колбэки: экран фичи получает лямбды вроде
onOpenOrder: (String) -> Unit, а с конкретным переходом их связывает:app; - интерфейс навигатора и DI: в
apiлежитinterface ProfileNavigator, реализация вimplили в:app, подключается через Hilt. Удобно для фрагментов и для тестов; - диплинки:
navDeepLinkс URI развязывает модули полностью, но теряет проверку типов при компиляции. Их оставляют для push-уведомлений и ссылок извне.
В приложениях на фрагментах то же делают вложенными графами через <include> и переходами между модулями по диплинку. Для новых проектов на Compose стоит упомянуть Navigation 3, где стек переходов это обычный список, которым управляет ваш код.
Где спотыкаются
Делают прямую зависимость одной фичи от другой ради одного перехода. Граф модулей превращается в клубок, и правка одной фичи пересобирает половину проекта.
Передают через аргументы целые объекты. Маршрут должен нести идентификатор, а данные экран загружает сам из репозитория: иначе на больших данных получите TransactionTooLargeException, а на экране устаревшую копию вместо актуального состояния.
Строят навигацию на строковых маршрутах вида "profile/{id}", собранных вручную. Опечатка в одном месте всплывает только в рантайме, падением при переходе.