Navigation 3 в Compose Multiplatform
Библиотека Navigation для Android обновилась до Navigation 3. В ней представлен переработанный подход к навигации, который работает с Compose и учитывает отзывы о предыдущей версии библиотеки. Начиная с версии 1.10, Compose Multiplatform поддерживает использование Navigation 3 в мультиплатформенных проектах на всех поддерживаемых платформах: Android, iOS, настольных платформах и в вебе.
Основные изменения
Navigation 3 — это не просто новая версия библиотеки: во многих отношениях это совершенно новая библиотека. Подробнее о принципах, лежащих в основе этой переработки, читайте в публикации в блоге Android Developers.
К основным изменениям в Navigation 3 относятся:
Стек возврата под управлением пользователя. Вместо использования единого стека возврата библиотеки вы создаёте и управляете
SnapshotStateListсостояний, за которым интерфейс наблюдает напрямую.Низкоуровневые строительные блоки. Благодаря более тесной интеграции с Compose библиотека предоставляет больше возможностей для реализации собственных компонентов и поведения навигации.
Адаптивная система компоновки. Адаптивный дизайн позволяет одновременно отображать несколько пунктов назначения и плавно переключаться между вариантами компоновки.
Подробнее об общем устройстве Navigation 3 см. в документации Android.
Настройка зависимостей
Чтобы попробовать мультиплатформенную реализацию Navigation 3, добавьте следующую зависимость в каталог версий:
[versions]
multiplatform-nav3-ui = "1.1.1"
[libraries]
jetbrains-navigation3-ui = { module = "org.jetbrains.androidx.navigation3:navigation3-ui", version.ref = "multiplatform-nav3-ui" }
Если проект использует библиотеки Material 3 Adaptive и ViewModel, добавьте также следующие артефакты поддержки навигации:
[versions]
compose-multiplatform-adaptive = "1.3.0-beta02"
compose-multiplatform-lifecycle = "2.10.0"
[libraries]
jetbrains-material3-adaptiveNavigation3 = { module = "org.jetbrains.compose.material3.adaptive:adaptive-navigation3", version.ref = "compose-multiplatform-adaptive" }
jetbrains-lifecycle-viewmodelNavigation3 = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "compose-multiplatform-lifecycle" }
Наконец, вы можете попробовать библиотеку для проверки концепции, созданную инженером JetBrains. Она интегрирует мультиплатформенную Navigation 3 с навигацией по истории браузера в вебе:
[versions]
compose-multiplatform-navigation3-browser = "1.1.0"
[libraries]
navigation3-browser = { module = "com.github.terrakok:navigation3-browser", version.ref = "compose-multiplatform-navigation3-browser" }
Поддержка навигации по истории браузера в базовой мультиплатформенной библиотеке Navigation 3 запланирована для будущего выпуска. Следите за ходом работы в задаче CMP-8924.
Мультиплатформенная поддержка
Navigation 3 тесно связана с Compose, поэтому Android-реализацию навигации можно использовать в общем коде Compose Multiplatform с минимальными изменениями. Для поддержки платформ без JVM, таких как веб и iOS, необходимо реализовать полиморфную сериализацию ключей пунктов назначения.
На GitHub можно сравнить подробные примеры Android-приложений и мультиплатформенных приложений, использующих Navigation 3:
Полиморфная сериализация ключей пунктов назначения
На Android Navigation 3 использует сериализацию на основе рефлексии, которая недоступна для платформ без JVM, например iOS. Чтобы обойти это ограничение, в библиотеке есть две перегрузки функции rememberNavBackStack():
Первая перегрузка принимает только набор ссылок
NavKeyи требует сериализатор на основе рефлексии.Вторая перегрузка также принимает параметр
SavedStateConfiguration, который позволяет указатьSerializersModuleи обрабатывать открытую полиморфную сериализацию на всех платформах.
Мультиплатформенные примеры Navigation 3 объявляют маршруты и регистрируют их с помощью SavedStateConfiguration, как показано ниже:
@Serializable
private data object RouteA : NavKey
@Serializable
private data class RouteB(val id: String) : NavKey
// Creates the required serialization configuration for open polymorphism
private val config = SavedStateConfiguration {
serializersModule = SerializersModule {
polymorphic(NavKey::class) {
subclass(RouteA::class, RouteA.serializer())
subclass(RouteB::class, RouteB.serializer())
}
}
}
@Composable
fun BasicDslActivity() {
// Consumes the serialization configuration
val backStack = rememberNavBackStack(config, RouteA)
NavDisplay(
backStack = backStack,
//...
)
}
Рекомендуемые подходы к сериализации
При реализации мультиплатформенной навигации необходимо выбрать, как организовать и сериализовать определения маршрутов. В зависимости от сложности проекта и его модульной структуры используйте один из следующих трёх шаблонов.
Один модуль с запечатанным типом
Для небольших проектов, в которых все маршруты находятся в одном модуле, используйте sealed interface. Это самый простой подход, поскольку Kotlin Serialization автоматически обрабатывает иерархию:
@Serializable
sealed interface Route : NavKey
@Serializable
data object RouteA : Route
@Serializable
data class RouteB(val id: String) : Route
// Backstack with default serializer
val backStack: MutableList<Route> =
rememberSerializable(serializer = SnapshotStateListSerializer()) {
mutableStateListOf(RouteA)
}
Также можно явно использовать функцию rememberNavBackStack(). В таком случае конфигурация будет немного отличаться:
private val config = SavedStateConfiguration {
serializersModule = SerializersModule {
polymorphic(NavKey::class) {
subclassesOfSealed<Route>()
}
}
}
val backStack = rememberNavBackStack(config, RouteA)
Несколько модулей с объединёнными запечатанными типами
В более сложных проектах, где маршруты определены в нескольких модулях, можно определить запечатанный тип для каждого модуля. Затем объедините сериализаторы в модуле app с помощью функции subclassesOfSealed().
// Module A
@Serializable sealed interface FeatureA : NavKey
@Serializable data object RouteA1 : FeatureA
@Serializable data object RouteA2 : FeatureA
// Module B
@Serializable sealed interface FeatureB : NavKey
@Serializable data class RouteB1(val id: String) : FeatureB
@Serializable data class RouteB2(val id: String) : FeatureB
// Module app
private val config = SavedStateConfiguration {
serializersModule = SerializersModule {
polymorphic(NavKey::class) {
subclassesOfSealed<FeatureA>()
subclassesOfSealed<FeatureB>()
}
}
}
val backStack = rememberNavBackStack(config, RouteA1)
При использовании внедрения зависимостей (DI) можно также использовать контейнеры DI, чтобы динамически собирать сериализаторы запечатанных типов из каждого модуля в Set<KSerializer>.
Несколько модулей с отдельной регистрацией маршрутов
Если маршруты нельзя сгруппировать в запечатанные типы, можно вручную объединить экземпляры SerializersModule из разных модулей.
// Module A
@Serializable data object RouteA1 : NavKey
@Serializable data object RouteA2 : NavKey
val serializerModuleA = SerializersModule {
polymorphic(NavKey::class) {
subclass(RouteA1::class, RouteA1.serializer())
subclass(RouteA2::class, RouteA2.serializer())
}
}
// Module B
@Serializable data class RouteB1(val id: String) : NavKey
@Serializable data class RouteB2(val id: String) : NavKey
val serializerModuleB = SerializersModule {
polymorphic(NavKey::class) {
subclass(RouteB1::class, RouteB1.serializer())
subclass(RouteB2::class, RouteB2.serializer())
}
}
// Module app
private val config = SavedStateConfiguration {
serializersModule = serializerModuleA + serializerModuleB
}
val backStack = rememberNavBackStack(config, RouteA1)
Этот подход обеспечивает высокую гибкость и слабую связанность, однако требует большего объёма ручного сопровождения. Как и в подходе с несколькими модулями с объединёнными запечатанными типами, для динамического формирования списка сериализаторов можно использовать DI, что повысит гибкость.
Что дальше
Подробная информация о Navigation 3 доступна на портале Android Developers. Хотя в некоторых разделах документации используются специфичные для Android примеры, основные понятия и принципы навигации одинаковы для всех платформ:
Обзор Navigation 3 с рекомендациями по управлению состоянием, модульной организации кода навигации и анимации.
Переход с Navigation 2 на Navigation 3. Navigation 3 проще воспринимать как новую библиотеку, а не новую версию существующей, поэтому речь идёт скорее о переписывании, чем о переходе. Тем не менее в руководстве описаны основные шаги.
© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/multiplatform/compose-navigation-3.html