Spec-Zone.ru › Kotlin 2

Глубокие ссылки

Механизм глубоких ссылок позволяет операционной системе обрабатывать специальные ссылки, перенаправляя пользователя в определённое место соответствующего приложения.

Глубокие ссылки — более общий случай ссылок на приложения (так их называют в Android) или универсальных ссылок (термин iOS): это проверенные связи приложения с определённым веб-адресом. Подробнее о них см. в документации по ссылкам на приложения Android и универсальным ссылкам iOS.

Глубокие ссылки также могут быть полезны для передачи в приложение внешних данных, например при авторизации OAuth: можно проанализировать глубокую ссылку и получить токен OAuth, не перенаправляя пользователя на другой экран.

Поскольку внешние данные могут быть вредоносными, обязательно следуйте рекомендациям по безопасности, чтобы должным образом снизить риски, связанные с обработкой URI глубоких ссылок без предварительной проверки.

Чтобы реализовать глубокую ссылку в Compose Multiplatform:

  1. Зарегистрируйте схему глубокой ссылки в конфигурации приложения

  2. Назначьте определённые глубокие ссылки пунктам назначения в графе навигации

  3. Обрабатывайте глубокие ссылки, полученные приложением

Настройка

Чтобы использовать глубокие ссылки с 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.

    Compose Multiplatform предоставляет DSL для Gradle, позволяющий добавлять значения в Info.plist приложения macOS. Для iOS можно отредактировать файл непосредственно в проекте KMP или зарегистрировать схемы с помощью графического интерфейса Xcode.

  • Для приложений Windows схемы глубоких ссылок можно объявить, добавив ключи с необходимыми сведениями в реестр Windows (для Windows 8 и более ранних версий) или указав расширение в манифесте пакета (для Windows 10 и 11). Это можно сделать с помощью сценария установки или стороннего генератора пакетов дистрибутива, например Hydraulic Conveyor. Compose Multiplatform не поддерживает настройку этого параметра непосредственно в проекте.

    Убедитесь, что вы не используете одну из схем, зарезервированных Windows.

  • В 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}" },
        )
    ) { ... }

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

Обработка полученных глубоких ссылок

В Android URI глубоких ссылок, отправленные приложению, доступны как часть Intent, вызвавшего переход по глубокой ссылке. Для кроссплатформенной реализации нужен универсальный способ прослушивания глубоких ссылок.

Создадим простейшую реализацию:

  1. Объявите в общем коде синглтон для хранения и кэширования URI, а также слушатель внешних URI.

  2. При необходимости реализуйте платформенные вызовы, передающие URI, полученные от операционной системы.

  3. Настройте слушатель новых глубоких ссылок в главной композируемой функции.

Объявление синглтона со слушателем 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
    }

Сведения о соглашениях об именовании при обращении к синглтонам из Swift см. в документации Kotlin/Native.

Настройка слушателя

Для настройки слушателя и его очистки после завершения работы композируемой функции можно использовать 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.

21 июля 2026 г.
Навигация и маршрутизацияОперации перетаскивания

© 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

Spec-Zone.ru

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