Spec-Zone.ru › Kotlin 2

Использование платформенных API

В этой статье вы узнаете, как использовать платформенные API при разработке многоплатформенных приложений и библиотек.

Многоплатформенные библиотеки Kotlin

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

Уже доступно множество библиотек, которые можно использовать для реализации сетевого взаимодействия, ведения журналов и аналитики, а также для доступа к функциям устройства и многого другого. Ищите библиотеки на klibs.io — платформе для поиска библиотек Kotlin Multiplatform.

Ожидаемые и фактические функции и свойства

В Kotlin есть языковой механизм для доступа к платформенным API при разработке общей логики: ожидаемые и фактические объявления.

С помощью этого механизма общий исходный набор многоплатформенного модуля определяет ожидаемое объявление, а каждый платформенный исходный набор должен предоставить соответствующее ему фактическое объявление. Компилятор проверяет, что для каждого объявления, помеченного ключевым словом expect в общем исходном наборе, имеются соответствующие объявления, помеченные ключевым словом actual во всех целевых платформенных исходных наборах.

Этот механизм работает с большинством объявлений Kotlin, например с функциями, классами, интерфейсами, перечислениями, свойствами и аннотациями. В этом разделе рассматривается использование ожидаемых и фактических функций и свойств.

Using expected and actual functions and properties

В этом примере вы объявите ожидаемую функцию platform() в общем исходном наборе и предоставите фактические реализации в платформенных исходных наборах. При генерации кода для конкретной платформы компилятор Kotlin объединяет ожидаемые и фактические объявления. Он генерирует одну функцию platform() с её фактической реализацией. Ожидаемые и фактические объявления следует определить в одном пакете, чтобы в результирующем коде платформы они объединились в одно объявление. Любой вызов ожидаемой функции platform() в сгенерированном коде платформы будет обращаться к соответствующей фактической реализации.

Пример: генерация UUID

Предположим, что вы разрабатываете приложения для iOS и Android с помощью Kotlin Multiplatform и хотите сгенерировать универсальный уникальный идентификатор (UUID).

Для этого объявите ожидаемую функцию randomUUID() с ключевым словом expect в общем исходном наборе вашего модуля Kotlin Multiplatform. Не добавляйте код реализации.

// In the common source set:
expect fun randomUUID(): String

В каждом платформенном исходном наборе (iOS и Android) предоставьте фактическую реализацию функции randomUUID(), ожидаемой в общем модуле. Пометьте эти фактические реализации ключевым словом actual.

Generating UUID with expected and actual declarations

В следующих фрагментах показаны реализации для Android и iOS. В платформенном коде используется ключевое слово actual и то же имя функции:

// In the android source set:
import java.util.*

actual fun randomUUID() = UUID.randomUUID().toString()
// In the iOS source set:
import platform.Foundation.NSUUID

actual fun randomUUID(): String = NSUUID().UUIDString()

В реализации для Android используются API, доступные на Android, а в реализации для iOS — API, доступные на iOS. Вы можете обращаться к API iOS из кода Kotlin/Native.

При создании результирующего кода платформы для Android компилятор Kotlin автоматически объединяет ожидаемые и фактические объявления и генерирует одну функцию randomUUID() с её фактической реализацией для Android. Для iOS выполняется тот же процесс.

Для простоты в этом и следующих примерах используются упрощённые названия исходных наборов: «common», «ios» и «android». Обычно это означает commonMain, iosMain и androidMain; аналогичную логику можно определить в тестовых исходных наборах commonTest, iosTest и androidTest.

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

Интерфейсы в общем коде

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

Using interfaces

В реализациях платформенных исходных наборов используются соответствующие им зависимости:

// In the commonMain source set:
interface Platform {
    val name: String
}
// In the androidMain source set:
import android.os.Build

class AndroidPlatform : Platform {
    override val name: String = "Android ${Build.VERSION.SDK_INT}"
}
// In the iosMain source set:
import platform.UIKit.UIDevice

class IOSPlatform : Platform {
    override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
}

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

  • Использование ожидаемых и фактических функций

  • Предоставление реализаций через разные точки входа

  • Использование фреймворка внедрения зависимостей

Ожидаемые и фактические функции

Определите ожидаемую функцию, возвращающую значение этого интерфейса, а затем определите фактические функции, возвращающие его подклассы:

// In the commonMain source set:
interface Platform

expect fun platform(): Platform
// In the androidMain source set:
class AndroidPlatform : Platform

actual fun platform() = AndroidPlatform()
// In the iosMain source set:
class IOSPlatform : Platform

actual fun platform() = IOSPlatform()

При вызове функции platform() в общем коде она может работать с объектом типа Platform. При запуске этого общего кода на Android вызов platform() возвращает экземпляр класса AndroidPlatform. При запуске на iOS функция platform() возвращает экземпляр класса IOSPlatform.

Разные точки входа

Если вы контролируете точки входа, можно создавать реализации для каждого платформенного артефакта, не используя ожидаемые и фактические объявления. Для этого определите платформенные реализации в общем модуле Kotlin Multiplatform, а создавайте их экземпляры в платформенных модулях:

// Shared Kotlin Multiplatform module
// In the commonMain source set:
interface Platform

fun application(p: Platform) {
    // application logic
}
// In the androidMain source set:
class AndroidPlatform : Platform
// In the iosMain source set:
class IOSPlatform : Platform
// In the androidApp platform module:
import android.app.Application
import mysharedpackage.*

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        application(AndroidPlatform())
    }
}
// In the iosApp platform module (in Swift):
import shared

@main
struct iOSApp : App {
    init() {
        application(IOSPlatform())
    }
}

На Android следует создать экземпляр AndroidPlatform и передать его функции application(), а на iOS аналогичным образом создать экземпляр IOSPlatform и передать его. Эти точки входа не обязательно должны быть точками входа ваших приложений, но именно в них можно вызывать определённые функции общего модуля.

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

Фреймворк внедрения зависимостей

В современных приложениях обычно используют фреймворк внедрения зависимостей (DI) для создания слабосвязанной архитектуры. Фреймворк DI позволяет внедрять зависимости в компоненты с учётом текущего окружения.

Любой фреймворк DI, поддерживающий Kotlin Multiplatform, поможет внедрять разные зависимости для разных платформ.

Например, Koin — это фреймворк внедрения зависимостей с поддержкой Kotlin Multiplatform:

// In the common source set:
import org.koin.dsl.module

interface Platform

expect val platformModule: Module
// In the androidMain source set:
class AndroidPlatform : Platform

actual val platformModule: Module = module {
    single<Platform> {
        AndroidPlatform()
    }
}
// In the iosMain source set:
class IOSPlatform : Platform

actual val platformModule = module {
    single<Platform> { IOSPlatform() }
}

Здесь DSL Koin создают модули, в которых определяются компоненты для внедрения. Объявите модуль в общем коде с помощью ключевого слова expect, а затем предоставьте платформенную реализацию для каждой платформы с помощью ключевого слова actual. Фреймворк самостоятельно выбирает правильную реализацию во время выполнения.

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

Кроме того, общий интерфейс не обязательно реализовывать на Kotlin. Это можно сделать и на другом языке, например Swift, в отдельном платформенном модуле. Если вы выберете этот подход, следует предоставить реализацию из платформенного модуля iOS с помощью фреймворка DI:

Using dependency injection framework

Этот подход работает, только если реализации находятся в платформенных модулях. Он не очень хорошо масштабируется: модуль Kotlin Multiplatform не может быть самодостаточным, а общий интерфейс придётся реализовывать в другом модуле.

Что дальше?

Дополнительные примеры и сведения о механизме expect/actual см. в разделе Ожидаемые и фактические объявления.

27 июля 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/multiplatform-connect-to-apis.html

Spec-Zone.ru

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