0Pricing
Kotlin Academy · Урок

Механизм expect/actual для API платформ

Объявляйте ожидаемые объявления в общем коде и предоставляйте фактические реализации для каждой платформы.

«Механизм expect/actual для API платформ» — бесплатный урок Kotlin Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Kotlin Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Kotlin Academy содержит 4 уроков всего.

Паттерн ожидаемой и фактической реализации

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

// commonMain:
expect fun platformName(): String

// androidMain:
actual fun platformName(): String = "Android ${android.os.Build.VERSION.SDK_INT}"

// iosMain:
actual fun platformName(): String = UIDevice.currentDevice.systemName()

Ожидаемый класс

expect class объявляет класс с его API в commonMain. Каждая платформа предоставляет полную реализацию.

// commonMain:
expect class DateTime {
    fun format(pattern: String): String
    val timestamp: Long
}

// androidMain:
actual class DateTime {
    actual val timestamp = System.currentTimeMillis()
    actual fun format(pattern: String): String =
        java.text.SimpleDateFormat(pattern).format(java.util.Date(timestamp))
}

Ожидаемый объект

expect object объявляет одиночный объект с его контрактом. Это удобно для платформенных утилит, например журналирования, настроек или аналитики.

// commonMain:
expect object Logger {
    fun debug(msg: String)
    fun error(msg: String, cause: Throwable? = null)
}

// androidMain:
actual object Logger {
    actual fun debug(msg: String) = android.util.Log.d("App", msg)
    actual fun error(msg: String, cause: Throwable?) =
        android.util.Log.e("App", msg, cause)
}

Ожидаемое свойство или функция верхнего уровня

Ожидаемые свойства expect val и функции expect fun верхнего уровня просты и хорошо подходят для платформенных утилит без состояния.

// commonMain:
expect val isDebugBuild: Boolean
expect fun generateUUID(): String

// androidMain:
actual val isDebugBuild: Boolean = BuildConfig.DEBUG
actual fun generateUUID(): String = java.util.UUID.randomUUID().toString()

// iosMain:
actual val isDebugBuild: Boolean = false // or a native flag
actual fun generateUUID(): String = NSUUID().UUIDString()

Ожидаемая функция со стандартной реализацией

Начиная с Kotlin 1.9, expect fun может иметь стандартную реализацию через expect fun f() { }. Платформы могут переопределить её или использовать стандартную реализацию.

// commonMain (Kotlin 1.9+):
expect fun log(msg: String) {
    println("[DEFAULT] $msg") // used if no actual overrides
}

// androidMain:
actual fun log(msg: String) = android.util.Log.d("KMP", msg)

// iosMain (uses default):
// no actual needed — default is used

Фактический псевдоним типа

Если на платформе уже есть именно тот класс, который нужен, используйте actual typealias, чтобы сослаться на него, вместо создания обёртки.

// commonMain:
expect class AtomicInt {
    fun get(): Int
    fun incrementAndGet(): Int
}

// androidMain / jvmMain:
actual typealias AtomicInt = java.util.concurrent.atomic.AtomicInteger

// iosMain:
actual class AtomicInt {
    private var value = 0
    actual fun get() = value
    actual fun incrementAndGet() = ++value
}

Ожидаемый интерфейс (косвенный паттерн)

Для большей гибкости объявите интерфейс в commonMain, а expect/actual используйте только для фабрики, создающей экземпляр платформы.

// commonMain:
interface FileStorage {
    suspend fun readFile(path: String): ByteArray
    suspend fun writeFile(path: String, data: ByteArray)
}
expect fun createFileStorage(): FileStorage

// androidMain:
actual fun createFileStorage(): FileStorage = AndroidFileStorage()

// iosMain:
actual fun createFileStorage(): FileStorage = IosFileStorage()

Тестирование ожидаемых и фактических реализаций

В commonTest тестируйте общий API. Фактические реализации запускаются во время платформенных задач тестирования (testDebugUnitTest на Android и iosX64Test на iOS).

// commonTest:
import kotlin.test.Test
import kotlin.test.assertNotNull
class PlatformTest {
    @Test
    fun testUUID() {
        val uuid = generateUUID()
        assertNotNull(uuid)
        println("UUID: $uuid")
    }
}

Распространённые ошибки

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

// If you add a new target (e.g., jvmMain), you must add:
// actual fun platformName(): String = "JVM"
// Otherwise:
// error: Expected function platformName has no actual declaration in module :shared for JVM
fun main() { println("Missing actuals = compile error") }

Класс ожидаемой аннотации

expect annotation class позволяет сопоставлять платформенные аннотации — например, сопоставить общую аннотацию, подобную @Parcelize, с плагином Parcelize для Android.

// commonMain:
expect annotation class CommonParcelize()

// androidMain:
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
actual annotation class CommonParcelize

// iosMain:
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
actual annotation class CommonParcelize

Практический пример: Keychain и Preferences

Типичный практический пример expect/actual — интерфейс защищённого хранилища, в котором Android использует EncryptedSharedPreferences, а iOS — Keychain.

// commonMain:
expect class SecureStorage {
    fun save(key: String, value: String)
    fun get(key: String): String?
}

// androidMain:
actual class SecureStorage {
    private val prefs = /* EncryptedSharedPreferences */ mapOf<String,String>()
    actual fun save(key: String, value: String) { /* ... */ }
    actual fun get(key: String): String? = null // simplified
}

Быстрая проверка

Что произойдёт во время компиляции, если для объявления expect не предусмотрена actual для некоторой целевой платформы?

Итоги

Ожидаемая декларация объявляет API в commonMain. Фактическая реализация предоставляет реализацию платформы. Используйте Фактический псевдоним типа, если на платформе уже есть нужный тип. Отсутствующие фактические реализации обнаруживаются во время компиляции — неожиданных проблем во время выполнения не будет.

Часто задаваемые вопросы

Урок «Механизм expect/actual для API платформ» бесплатный?

Да — полный текст урока «Механизм expect/actual для API платформ» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Kotlin Academy, подпишись на CoddyKit PRO. Курс Kotlin Academy содержит 4 уроков всего.

Чему я научусь в уроке «Механизм expect/actual для API платформ»?

Объявляйте ожидаемые объявления в общем коде и предоставляйте фактические реализации для каждой платформы. Ты практикуешь Kotlin Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Kotlin Academy?

Предыдущий опыт не требуется. Kotlin Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Механизм expect/actual для API платформ»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Kotlin Academy?

Да. Каждый урок Kotlin Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Структура проекта KMP: commonMain, androidMain, iosMain
  2. Механизм expect/actual для API платформ
  3. Общие слои репозитория и вариантов использования
  4. Внедрение зависимостей в KMP с Koin
← Назад к Kotlin Academy