0Pricing
Android Academy · Урок

Навигация между модулями

Соединяйте функции без жёсткой связанности

«Навигация между модулями» — бесплатный урок 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 — локальная установка не требуется.

Все уроки этого курса

  1. Зачем нужна модульность
  2. Модули функций и ядра
  3. Управление зависимостями модулей
  4. Навигация между модулями
← Назад к Android Academy