Spec-Zone.ru › Kotlin 2

Навигация и маршрутизация

Навигация — важная часть приложений с пользовательским интерфейсом, которая позволяет пользователям переходить между экранами приложения. Compose Multiplatform использует подход Jetpack Compose к навигации.

Настройка

Чтобы использовать библиотеку Navigation, добавьте следующую зависимость в исходный набор commonMain:

kotlin { // ... sourceSets { // ... commonMain.dependencies { // ... implementation("org.jetbrains.androidx.navigation:navigation-compose:2.10.0-alpha02") } // ... } }

Пример проекта

Чтобы увидеть библиотеку навигации Compose Multiplatform в действии, ознакомьтесь с проектом nav_cupcake, созданным на основе Android-практикума «Переход между экранами с помощью Compose». Более сложный пример можно найти в официальном приложении KotlinConf.

Как и в Jetpack Compose, для реализации навигации необходимо:

  1. Перечислить маршруты, которые должны быть включены в граф навигации. Каждый маршрут должен быть уникальной строкой, определяющей путь.

  2. Создать экземпляр NavHostController в качестве основного компонуемого свойства для управления навигацией.

  3. Добавить компонуемый элемент NavHost в приложение:

    1. Выбрать начальный пункт назначения из списка маршрутов, определённых ранее.

    2. Создать граф навигации напрямую — при создании 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:

  1. Передайте необязательную лямбду getBackStackEntryRoute в функцию navController.bindToBrowserNavigation(), чтобы задать правила преобразования маршрутов во фрагменты URL при необходимости.

  2. При необходимости добавьте код, который перехватывает фрагменты URL в адресной строке (когда пользователь нажимает на URL приложения или вставляет его) и преобразует URL в маршруты, чтобы соответствующим образом выполнять навигацию.

Ниже приведён пример простого типобезопасного графа навигации для следующих примеров веб-кода (commonMain/kotlin/org.example.app/App.kt):

// Serializable object and classes for route arguments in the navigation graph @Serializable data object StartScreen @Serializable data class Id(val id: Long) @Serializable data class Patient(val name: String, val age: Long) @Composable internal fun App( onNavHostReady: suspend (NavController) -> Unit = {} ) = AppTheme { val navController = rememberNavController() NavHost( navController = navController, startDestination = StartScreen ) { composable<StartScreen> { Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Text("Starting screen") // Button that opens the 'Id' screen with a suitable parameter Button(onClick = { navController.navigate(Id(222)) }) { Text("Pass 222 as a parameter to the ID screen") } // Button that opens the 'Patient' screen with suitable parameters Button(onClick = { navController.navigate(Patient( "Jane Smith-Baker", 33)) }) { Text("Pass 'Jane Smith-Baker' and 33 to the Person screen") } } } composable<Id> {...} composable<Patient> {...} } LaunchedEffect(navController) { onNavHostReady(navController) } }

В 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, браузер попытается обратиться к неверной конечной точке вместо передачи управления приложению.

Если 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() { ... }
                        }
                    )
                }
            }
        }
        
24 марта 2026 г.
Навигация 3 в Compose MultiplatformГлубокие ссылки

© 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API