Навигация между модулями
Соединяйте функции без жёсткой связанности
«Навигация между модулями» — бесплатный урок Android Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Android Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Android Academy содержит 4 уроков всего.
Задача навигации
Когда функциональности находятся в отдельных модулях, возникает новый вопрос: как :feature:home открыть экран из :feature:profile без зависимости от него? Если функциональности напрямую импортируют друг друга, возникает связанность и появляется риск циклов.
В этом уроке вы научитесь связывать функциональности через навигацию, сохраняя их независимость.
Где находится NavHost
Единственный NavHost находится в модуле :app — единственном месте, которому разрешено знать обо всех функциональностях. Каждая функциональность добавляет свои пункты назначения, а :app собирает их в один граф.
// app/AppNavHost.kt
@Composable
fun AppNavHost(navController: NavHostController = rememberNavController()) {
NavHost(navController, startDestination = HomeRoute) {
homeScreen(onProfileClick = { navController.navigate(ProfileRoute) })
profileScreen(onBack = { navController.popBackStack() })
}
}Маршруты с проверкой типов
Современная Navigation Compose поддерживает маршруты с проверкой типов: маршрут — это объект или класс данных @Serializable, а не строка с произвольным значением. Каждая функциональность определяет собственный тип маршрута в своём модуле и тем самым владеет своим контрактом навигации.
// feature/profile/ProfileRoute.kt
import kotlinx.serialization.Serializable
@Serializable
data class ProfileRoute(val userId: String)Функциональности предоставляют расширения NavGraphBuilder
Ключевой приём: каждая функциональность предоставляет функцию-расширение NavGraphBuilder, которая регистрирует пункт назначения. Функциональность владеет своим экраном, а :app лишь вызывает эту функцию. Функциональность никогда не ссылается на другие функциональности.
// feature/profile/ProfileNavigation.kt
fun NavGraphBuilder.profileScreen(onBack: () -> Unit) {
composable<ProfileRoute> { backStackEntry ->
val route: ProfileRoute = backStackEntry.toRoute()
ProfileScreen(userId = route.userId, onBack = onBack)
}
}Ослабление связанности с помощью обратных вызовов навигации
Функциональность не должна напрямую вызывать navigate(SomeOtherFeatureRoute), поскольку для этого потребовалась бы зависимость от другой функциональности. Вместо этого функциональность предоставляет обратные вызовы-лямбды, например onProfileClick. Модуль :app решает, куда именно они ведут.
// feature/home/HomeNavigation.kt
fun NavGraphBuilder.homeScreen(onProfileClick: (String) -> Unit) {
composable<HomeRoute> {
HomeScreen(onUserClick = { userId -> onProfileClick(userId) })
}
}
// :home does NOT know ProfileRoute exists.Модуль :app соединяет всё вместе
Только модуль :app знает обо всех маршрутах и связывает обратные вызовы с реальной навигацией. Это единственное место, где допустима связанность: вся его задача — сборка.
// app/AppNavHost.kt
NavHost(navController, startDestination = HomeRoute) {
homeScreen(
onProfileClick = { userId ->
navController.navigate(ProfileRoute(userId)) // app knows both
}
)
profileScreen(onBack = { navController.popBackStack() })
}Безопасная передача аргументов
Поскольку маршруты являются классами данных @Serializable, аргументы проверяются по типам во время компиляции. Вы получаете их обратно с помощью toRoute() внутри пункта назначения. Больше не нужны разбор строк и сбои во время выполнения из-за arguments?.getString(...).
// Reading arguments inside the destination
composable<ProfileRoute> { entry ->
val args: ProfileRoute = entry.toRoute()
ProfileScreen(userId = args.userId)
}
// Or directly in a ViewModel via SavedStateHandle
val route: ProfileRoute = savedStateHandle.toRoute()Передача типов маршрутов через модуль API
Иногда одной функциональности действительно нужно перейти к другой и получить тип её маршрута. Вместо зависимости от всей функциональности предоставьте только маршрут в небольшом модуле :feature:profile:api, содержащем только маршрут @Serializable. Тяжёлый модуль :impl остаётся закрытым.
// feature/profile/api -> only the route type
@Serializable
data class ProfileRoute(val userId: String)
// feature/home/build.gradle.kts
// home may depend on the lightweight api to build the route,
// but never on :feature:profile:impl
implementation(project(":feature:profile:api"))Вложенная навигация для каждой функциональности
Функциональность с несколькими экранами может предоставлять целый вложенный граф с помощью navigation<T>. Функциональность владеет своим внутренним сценарием, а :app лишь подключает граф через одну точку входа.
// feature/onboarding/OnboardingNavigation.kt
fun NavGraphBuilder.onboardingGraph(onFinished: () -> Unit) {
navigation<OnboardingGraph>(startDestination = WelcomeRoute) {
composable<WelcomeRoute> { WelcomeScreen() }
composable<PermissionsRoute> { PermissionsScreen(onDone = onFinished) }
}
}Глубокие ссылки между модулями
Маршруты с проверкой типов также поддерживают глубокие ссылки. Функциональность объявляет шаблон URI глубокой ссылки для своего пункта назначения, а NavHost модуля :app направляет входящую ссылку на нужный экран функциональности без импорта другой функциональности.
composable<ProfileRoute>(
deepLinks = listOf(
navDeepLink<ProfileRoute>(basePath = "https://myapp.com/profile")
)
) { entry ->
ProfileScreen(userId = entry.toRoute<ProfileRoute>().userId)
}Итоги шаблона ослабления связанности
В совокупности правила просты:
- Каждая функциональность владеет своим типом маршрута и расширением NavGraphBuilder.
- Функциональности передают намерение через обратные вызовы-лямбды, а не выполняют навигацию напрямую.
- Только :app знает обо всех функциональностях и связывает обратные вызовы с реальными маршрутами.
- Если маршрутом нужно поделиться, предоставьте его через небольшой модуль :api.
Так функциональности остаются независимыми, могут использовать кэш сборки и не содержат циклов.
Быстрая проверка
В многомодульном приложении :feature:home должен перевести пользователя на экран из :feature:profile. Как лучше всего сохранить независимость функциональностей?
Итоги: навигация между модулями
Вы научились связывать функциональности без жёсткой связанности:
- Единственный
NavHostнаходится в тонком модуле :app. - Каждая функциональность владеет маршрутом
@Serializableи расширениемNavGraphBuilder. - Функциональности предоставляют обратные вызовы, а не переходят напрямую к другим функциональностям.
- Делитесь маршрутом только через небольшой модуль :api, когда это действительно необходимо; глубокие ссылки и вложенные графы используют тот же шаблон.
На этом завершается тема многомодульной архитектуры приложения: теперь вы умеете разделять, связывать и организовывать навигацию в масштабируемой кодовой базе Android.
Часто задаваемые вопросы
Урок «Навигация между модулями» бесплатный?
Да — полный текст урока «Навигация между модулями» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Android Academy, подпишись на CoddyKit PRO. Курс Android Academy содержит 4 уроков всего.
Чему я научусь в уроке «Навигация между модулями»?
Соединяйте функции без жёсткой связанности Ты практикуешь Android Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Android Academy?
Предыдущий опыт не требуется. Android Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Навигация между модулями»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Android Academy?
Да. Каждый урок Android Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Зачем нужна модульность
- Модули функций и ядра
- Управление зависимостями модулей
- Навигация между модулями