Навигация и маршрутизация
Навигация — важная часть приложений с пользовательским интерфейсом, которая позволяет пользователям переходить между экранами приложения. Compose Multiplatform использует подход Jetpack Compose к навигации.
Настройка
Чтобы использовать библиотеку Navigation, добавьте следующую зависимость в исходный набор commonMain:
Пример проекта
Чтобы увидеть библиотеку навигации Compose Multiplatform в действии, ознакомьтесь с проектом nav_cupcake, созданным на основе Android-практикума «Переход между экранами с помощью Compose». Более сложный пример можно найти в официальном приложении KotlinConf.
Как и в Jetpack Compose, для реализации навигации необходимо:
Перечислить маршруты, которые должны быть включены в граф навигации. Каждый маршрут должен быть уникальной строкой, определяющей путь.
Создать экземпляр
NavHostControllerв качестве основного компонуемого свойства для управления навигацией.-
Добавить компонуемый элемент
NavHostв приложение:Выбрать начальный пункт назначения из списка маршрутов, определённых ранее.
Создать граф навигации напрямую — при создании
NavHost— или программно, используя функциюNavController.createGraph().
Каждая запись в стеке возврата (каждый маршрут навигации, включённый в граф) реализует интерфейс LifecycleOwner. При переходе между экранами приложения её состояние меняется с RESUMED на STARTED и обратно. Состояние RESUMED также называют «устоявшимся»: навигация считается завершённой, когда новый экран подготовлен и активен. Подробнее о текущей реализации в Compose Multiplatform см. на странице «Жизненный цикл».
Поддержка навигации браузера в веб-приложениях
Compose Multiplatform для веба полностью поддерживает общие API библиотеки Navigation и позволяет приложениям получать команды навигации из браузера. Пользователи могут нажимать кнопки Назад и Вперёд в браузере, чтобы переходить между маршрутами навигации, отражёнными в истории браузера, а также использовать адресную строку, чтобы понимать, где они находятся, и напрямую переходить к нужному пункту назначения.
Чтобы связать веб-приложение с графом навигации, определённым в общем коде, можно использовать метод NavController.bindToBrowserNavigation() в коде Kotlin/Wasm. Тот же метод можно использовать в Kotlin/JS, обернув его в блок onWasmReady {}, чтобы гарантировать инициализацию приложения Wasm и готовность Skia к отрисовке графики. Ниже приведён пример настройки:
//commonMain source set
@Composable
fun App(
onNavHostReady: suspend (NavController) -> Unit = {}
) {
val navController = rememberNavController()
NavHost(...) {
//...
}
LaunchedEffect(navController) {
onNavHostReady(navController)
}
}
//wasmJsMain source set
@OptIn(ExperimentalComposeUiApi::class)
@ExperimentalBrowserHistoryApi
fun main() {
val body = document.body ?: return
ComposeViewport(body) {
App(
onNavHostReady = { it.bindToBrowserNavigation() }
)
}
}
//jsMain source set
@OptIn(ExperimentalComposeUiApi::class)
@ExperimentalBrowserHistoryApi
fun main() {
onWasmReady {
val body = document.body ?: return@onWasmReady
ComposeViewport(body) {
App(
onNavHostReady = { it.bindToBrowserNavigation() }
)
}
}
}
После вызова navController.bindToBrowserNavigation():
URL в браузере отражает текущий маршрут (во фрагменте URL после символа
#).Приложение анализирует URL, введённые вручную, и преобразует их в пункты назначения внутри приложения.
По умолчанию при использовании типобезопасной навигации пункт назначения преобразуется во фрагмент URL в соответствии с kotlinx.serialization по умолчанию и добавленными аргументами: <app package>.<serializable type>/<argument1>/<argument2>. Например, example.org#org.example.app.StartScreen/123/Alice%2520Smith.
Настройка преобразования маршрутов в URL и обратно
Поскольку приложения Compose Multiplatform являются одностраничными, платформа изменяет адресную строку, имитируя обычную веб-навигацию. Чтобы сделать URL более понятными и отделить реализацию от шаблонов URL, можно задать имя экрана напрямую или разработать полностью собственную обработку маршрутов пунктов назначения:
-
Чтобы просто сделать URL понятным, используйте аннотацию
@SerialName, чтобы явно задать сериализуемое имя сериализуемого объекта или класса:// Instead of using the app package and object name, // this route will be translated to the URL simply as "#start" @Serializable @SerialName("start") data object StartScreen Чтобы полностью сформировать каждый URL, можно использовать необязательную лямбду
getBackStackEntryRoute.
Полная настройка URL
Чтобы реализовать полностью собственное преобразование маршрутов в URL:
Передайте необязательную лямбду
getBackStackEntryRouteв функциюnavController.bindToBrowserNavigation(), чтобы задать правила преобразования маршрутов во фрагменты URL при необходимости.При необходимости добавьте код, который перехватывает фрагменты URL в адресной строке (когда пользователь нажимает на URL приложения или вставляет его) и преобразует URL в маршруты, чтобы соответствующим образом выполнять навигацию.
Ниже приведён пример простого типобезопасного графа навигации для следующих примеров веб-кода (commonMain/kotlin/org.example.app/App.kt):
В wasmJsMain/kotlin/main.kt добавьте лямбду в вызов .bindToBrowserNavigation():
@OptIn(
ExperimentalComposeUiApi::class,
ExperimentalBrowserHistoryApi::class,
ExperimentalSerializationApi::class
)
fun main() {
val body = document.body ?: return
ComposeViewport(body) {
App(
onNavHostReady = { navController ->
navController.bindToBrowserNavigation() { entry ->
val route = entry.destination.route.orEmpty()
when {
// Identifies the route using its serial descriptor
route.startsWith(StartScreen.serializer().descriptor.serialName) -> {
// Sets the corresponding URL fragment to "#start"
// instead of "#org.example.app.StartScreen"
//
// This string must always start with the `#` character to keep
// the processing at the front end
"#start"
}
route.startsWith(Id.serializer().descriptor.serialName) -> {
// Accesses the route arguments
val args = entry.toRoute<Id>()
// Sets the corresponding URL fragment to "#find_id_222"
// instead of "#org.example.app.ID%2F222"
"#find_id_${args.id}"
}
route.startsWith(Patient.serializer().descriptor.serialName) -> {
val args = entry.toRoute<Patient>()
// Sets the corresponding URL fragment to "#patient_Jane%20Smith-Baker_33"
// instead of "#org.company.app.Patient%2FJane%2520Smith-Baker%2F33"
"#patient_${args.name}_${args.age}"
}
// Doesn't set a URL fragment for all other routes
else -> ""
}
}
}
)
}
}
Если URL имеют собственный формат, необходимо добавить обратную обработку, сопоставляющую введённые вручную URL с маршрутами пунктов назначения. Код сопоставления должен выполняться до вызова navController.bindToBrowserNavigation(), который связывает адрес браузера с графом навигации:
@OptIn(
ExperimentalComposeUiApi::class,
ExperimentalBrowserHistoryApi::class,
ExperimentalSerializationApi::class
)
fun main() {
val body = document.body ?: return
ComposeViewport(body) {
App(
onNavHostReady = { navController ->
// Accesses the fragment substring of the current URL
val initRoute = window.location.hash.substringAfter('#', "")
when {
// Identifies the corresponding route and navigates to it
initRoute.startsWith("start") -> {
navController.navigate(StartScreen)
}
initRoute.startsWith("find_id") -> {
// Parses the string to extract route parameters before navigating to it
val id = initRoute.substringAfter("find_id_").toLong()
navController.navigate(Id(id))
}
initRoute.startsWith("patient") -> {
val name = initRoute.substringAfter("patient_").substringBefore("_")
val id = initRoute.substringAfter("patient_").substringAfter("_").toLong()
navController.navigate(Patient(name, id))
}
}
navController.bindToBrowserNavigation() { ... }
}
)
}
}
@OptIn(
ExperimentalComposeUiApi::class,
ExperimentalBrowserHistoryApi::class,
ExperimentalSerializationApi::class
)
fun main() {
onWasmReady {
val body = document.body ?: return@onWasmReady
ComposeViewport(body) {
App(
onNavHostReady = { navController ->
// Accesses the fragment substring of the current URL
val initRoute = window.location.hash.substringAfter('#', "")
when {
// Identifies the corresponding route and navigates to it
initRoute.startsWith("start") -> {
navController.navigate(StartScreen)
}
initRoute.startsWith("find_id") -> {
// Parses the string to extract route parameters before navigating to it
val id = initRoute.substringAfter("find_id_").toLong()
navController.navigate(Id(id))
}
initRoute.startsWith("patient") -> {
val name = initRoute.substringAfter("patient_").substringBefore("_")
val id = initRoute.substringAfter("patient_").substringAfter("_").toLong()
navController.navigate(Patient(name, id))
}
}
navController.bindToBrowserNavigation() { ... }
}
)
}
}
}
© 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-routing.html