Глубокие ссылки
Механизм глубоких ссылок позволяет операционной системе обрабатывать специальные ссылки, перенаправляя пользователя в определённое место соответствующего приложения.
Глубокие ссылки — более общий случай ссылок на приложения (так их называют в Android) или универсальных ссылок (термин iOS): это проверенные связи приложения с определённым веб-адресом. Подробнее о них см. в документации по ссылкам на приложения Android и универсальным ссылкам iOS.
Глубокие ссылки также могут быть полезны для передачи в приложение внешних данных, например при авторизации OAuth: можно проанализировать глубокую ссылку и получить токен OAuth, не перенаправляя пользователя на другой экран.
Чтобы реализовать глубокую ссылку в Compose Multiplatform:
Зарегистрируйте схему глубокой ссылки в конфигурации приложения
Назначьте определённые глубокие ссылки пунктам назначения в графе навигации
Настройка
Чтобы использовать глубокие ссылки с Compose Multiplatform, настройте зависимости следующим образом.
Укажите эти версии, библиотеки и плагины в каталоге Gradle:
[versions]
compose-multiplatform = "1.12.0"
agp = "8.9.0"
# The multiplatform Navigation library version with deep link support
androidx-navigation = "2.10.0-alpha02"
# Minimum Kotlin version to use with Compose Multiplatform 1.8.0
kotlin = "2.1.0"
# Serialization library necessary to implement type-safe routes
kotlinx-serialization = "1.7.3"
[libraries]
navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
[plugins]
multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
compose = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
android-application = { id = "com.android.application", version.ref = "agp" }
Добавьте дополнительные зависимости в build.gradle.kts общего модуля:
plugins {
// ...
alias(libs.plugins.kotlinx.serialization)
}
// ...
kotlin {
// ...
sourceSets {
commonMain.dependencies {
// ...
implementation(libs.androidx.navigation.compose)
implementation(libs.kotlinx.serialization.json)
}
}
}
Регистрация схем глубоких ссылок в операционной системе
В каждой операционной системе есть свой способ обработки глубоких ссылок. Надёжнее всего обратиться к документации для используемых вами целевых платформ:
Для приложений Android схемы глубоких ссылок объявляются как фильтры интентов в файле
AndroidManifest.xml. См. документацию Android, чтобы узнать, как правильно настроить фильтры интентов.-
Для приложений iOS и macOS схемы глубоких ссылок объявляются в файлах
Info.plistс помощью ключа CFBundleURLTypes. -
Для приложений Windows схемы глубоких ссылок можно объявить, добавив ключи с необходимыми сведениями в реестр Windows (для Windows 8 и более ранних версий) или указав расширение в манифесте пакета (для Windows 10 и 11). Это можно сделать с помощью сценария установки или стороннего генератора пакетов дистрибутива, например Hydraulic Conveyor. Compose Multiplatform не поддерживает настройку этого параметра непосредственно в проекте.
В Linux схемы глубоких ссылок можно зарегистрировать в файле
.desktop, включённом в дистрибутив.
Назначение глубоких ссылок пунктам назначения
У пункта назначения, объявленного как часть графа навигации, есть необязательный параметр deepLinks, который может содержать список соответствующих объектов NavDeepLink. Каждый NavDeeplink описывает шаблон URI, которому должен соответствовать пункт назначения. Можно определить несколько шаблонов URI, ведущих на один и тот же экран.
Количество глубоких ссылок, которые можно определить для маршрута, не ограничено.
Общие шаблоны URI для глубоких ссылок
Общий шаблон URI должен соответствовать URI целиком. Можно использовать заполнители параметров, чтобы извлекать их из полученного URI в пункте назначения.
Правила для общих шаблонов URI:
Предполагается, что URI без схемы начинаются с
http://илиhttps://. ПоэтомуuriPattern = "example.com"соответствуетhttp://example.comиhttps://example.com.{placeholder}соответствует одному или нескольким символам (example.com/name={name}соответствуетhttps://example.com/name=Bob). Чтобы сопоставить ноль или более символов, используйте подстановочный символ.*(example.com/name={.*}соответствуетhttps://example.com/name=, а также любому значениюname).-
Параметры для заполнителей пути обязательны при сопоставлении, а параметры для заполнителей запроса — необязательны. Например, шаблон
example.com/users/{id}?arg1={arg1}&arg2={arg2}:Не соответствует
http://www.example.com/users?arg1=one&arg2=two, так как отсутствует обязательная часть пути (id).Соответствует и
http://www.example.com/users/4?arg2=two, иhttp://www.example.com/users/4?arg1=one.Также соответствует
http://www.example.com/users/4?other=random, поскольку лишние параметры запроса не влияют на сопоставление.
Если у нескольких композируемых функций есть
navDeepLink, соответствующий полученному URI, поведение не определено. Убедитесь, что шаблоны глубоких ссылок не пересекаются. Если вам нужно, чтобы несколько композируемых функций обрабатывали один и тот же шаблон глубокой ссылки, добавьте параметры пути или запроса либо используйте промежуточный пункт назначения для предсказуемой маршрутизации пользователя.
Создание шаблона URI для типа маршрута
Можно не указывать шаблон URI полностью: библиотека Navigation может автоматически создать его на основе параметров маршрута.
Чтобы воспользоваться этим подходом, определите глубокую ссылку следующим образом:
composable<PlantDetail>(
deepLinks = listOf(
navDeepLink<PlantDetail>(basePath = "demo://example.com/plant")
)
) { ... }
Здесь PlantDetail — это тип маршрута, используемый для пункта назначения, а "plant" в basePath — сериализованное имя класса данных PlantDetail.
Остальная часть шаблона URI будет создана следующим образом:
Обязательные параметры добавляются как параметры пути (пример:
/{id})Параметры со значением по умолчанию (необязательные параметры) добавляются как параметры запроса (пример:
?name={name})Коллекции добавляются как параметры запроса (пример:
?items={value1}&items={value2})Порядок параметров соответствует порядку полей в определении маршрута.
Например, для такого типа маршрута:
@Serializable data class PlantDetail( val id: String, val name: String, val colors: List<String>, val latinName: String? = null, )
библиотека создаёт следующий шаблон URI:
<basePath>/{id}/{name}/?colors={color1}&colors={color2}&latinName={latinName}
Пример добавления глубоких ссылок к пункту назначения
В этом примере мы назначаем несколько глубоких ссылок пункту назначения, а затем извлекаем значения параметров из полученных URI:
@Serializable @SerialName("dlscreen") data class DeepLinkScreen(val name: String)
// ...
val firstBasePath = "demo://example1.org"
NavHost(
navController = navController,
startDestination = FirstScreen
) {
// ...
composable<DeepLinkScreen>(
deepLinks = listOf(
// This composable should handle links both for demo://example1.org and demo://example2.org
navDeepLink { uriPattern = "$firstBasePath?name={name}" },
navDeepLink { uriPattern = "demo://example2.org/name={name}" },
// The generated pattern only handles the parameters,
// so we add the serial name for the route type
navDeepLink<DeepLinkScreen>(basePath = "$firstBasePath/dlscreen"),
)
) {
// If the app receives the URI `demo://example1.org/dlscreen/Jane/`,
// it matches the generated URI pattern (name is a required parameter and is given in the path),
// and you can map it to the route type automatically
val deeplink: DeepLinkScreen = backStackEntry.toRoute()
val nameGenerated = deeplink.name
// If the app receives a URI matching only a general pattern,
// like `demo://example1.com/?name=Jane`
// you need to parse the URI directly
val nameGeneral = backStackEntry.arguments?.read { getStringOrNull("name") }
// Composable content
}
}
В веб-приложениях глубокие ссылки работают немного иначе: поскольку Compose Multiplatform для Web создаёт одностраничные приложения, нужно поместить все параметры шаблона URI глубокой ссылки во фрагмент URL (после символа #) и убедиться, что все параметры закодированы для URL.
Метод backStackEntry.toRoute() по-прежнему можно использовать для анализа параметров, если фрагмент URL соответствует правилам для шаблонов URI. Подробнее о доступе к URL и его анализе в веб-приложении, а также об особенностях навигации в браузере см. в разделе Поддержка навигации браузера в веб-приложениях.
composable<DeepLinkScreen>(
deepLinks = listOf(
// For the default Compose Multiplatform setup, localhost:8080
// is the local dev endpoint that runs with the wasmJsBrowserDevelopmentRun Gradle task
navDeepLink { uriPattern = "localhost:8080/#dlscreen%2F{name}" },
)
) { ... }
Обработка полученных глубоких ссылок
В Android URI глубоких ссылок, отправленные приложению, доступны как часть Intent, вызвавшего переход по глубокой ссылке. Для кроссплатформенной реализации нужен универсальный способ прослушивания глубоких ссылок.
Создадим простейшую реализацию:
Объявите в общем коде синглтон для хранения и кэширования URI, а также слушатель внешних URI.
При необходимости реализуйте платформенные вызовы, передающие URI, полученные от операционной системы.
Настройте слушатель новых глубоких ссылок в главной композируемой функции.
Объявление синглтона со слушателем URI
В commonMain объявите объект-синглтон на верхнем уровне:
object ExternalUriHandler {
// Storage for when a URI arrives before the listener is set up
private var cached: String? = null
var listener: ((uri: String) -> Unit)? = null
set(value) {
field = value
if (value != null) {
// When a listener is set and `cached` is not empty,
// immediately invoke the listener with the cached URI
cached?.let { value.invoke(it) }
cached = null
}
}
// When a new URI arrives, cache it.
// If the listener is already set, invoke it and clear the cache immediately.
fun onNewUri(uri: String) {
cached = uri
listener?.let {
it.invoke(uri)
cached = null
}
}
}
Реализация платформенных вызовов синглтона
Для настольной JVM и iOS необходимо явно передать URI, полученный от системы.
В jvmMain/.../main.kt разберите аргументы командной строки для каждой необходимой операционной системы и передайте полученный URI синглтону:
// Import the singleton
import org.company.app.ExternalUriHandler
fun main() {
if(System.getProperty("os.name").indexOf("Mac") > -1) {
Desktop.getDesktop().setOpenURIHandler { uri ->
ExternalUriHandler.onNewUri(uri.uri.toString())
}
}
else {
ExternalUriHandler.onNewUri(args.getOrNull(0).toString())
}
application {
// ...
}
}
Для iOS добавьте в код Swift вариант application(), обрабатывающий входящие URI:
// Imports the KMP module to access the singleton
import SharedUI
func application(
_ application: UIApplication,
open uri: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
// Sends the full URI on to the singleton
ExternalUriHandler.shared.onNewUri(uri: uri.absoluteString)
return true
}
Настройка слушателя
Для настройки слушателя и его очистки после завершения работы композируемой функции можно использовать DisposableEffect(Unit). Например:
internal fun App(navController: NavHostController = rememberNavController()) = AppTheme {
// The effect is produced only once, as `Unit` never changes
DisposableEffect(Unit) {
// Sets up the listener to call `NavController.navigate()`
// for the composable that has a matching `navDeepLink` listed
ExternalUriHandler.listener = { uri ->
navController.navigate(NavUri(uri))
}
// Removes the listener when the composable is no longer active
onDispose {
ExternalUriHandler.listener = null
}
}
// Reusing the example from earlier in this article
NavHost(
navController = navController,
startDestination = FirstScreen
) {
// ...
composable<DeepLinkScreen>(
deepLinks = listOf(
navDeepLink { uriPattern = "$firstBasePath?name={name}" },
navDeepLink { uriPattern = "demo://example2.com/name={name}" },
)
) {
// Composable content
}
}
}
Результат
Теперь можно проследить весь процесс: когда пользователь открывает URI demo://, операционная система сопоставляет его с зарегистрированной схемой. Затем:
Если приложение, обрабатывающее глубокую ссылку, закрыто, синглтон получает URI и кэширует его. Когда запускается главная композируемая функция, она обращается к синглтону и переходит по глубокой ссылке, соответствующей кэшированному URI.
Если приложение, обрабатывающее глубокую ссылку, открыто, слушатель уже настроен, поэтому после получения URI синглтоном приложение сразу переходит по этой ссылке.
Что дальше
Посмотрите проекты, демонстрирующие работу библиотеки навигации Compose Multiplatform:
Базовый пример: проект nav_cupcake, адаптированный из практического курса Android «Навигация между экранами с помощью Compose».
Продвинутый пример: официальное приложение KotlinConf.
© 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-deep-links.html