expect/actual — суштина, кључне речи KMM и како раде

Аутор: IT Sectr Објављено: 2026-06-05 Време читања: 8 мин

expect/actual — механизам Kotlin Multiplatform који омогућава декларисање АПИ-ја зависних од платформе у заједничком коду. Кључна реч expect ствара уговор функције, класе или особине у commonMain, а кључна реч actual пружа конкретну имплементацију за сваку платформу. Компајлер проверава да свакој expect декларацији одговара actual имплементација на свим циљним платформама. Према JetBrains, 2025, механизам се користи у 80% KMM пројеката за имплементацију пословне логике специфичне за платформу.

Главно

  • expect — кључна реч за декларисање уговора функције, класе или особине у заједничком коду.
  • actual — кључна реч за пружање имплементације платформе expect декларације.
  • commonMain — source set са заједничким кодом где се налазе expect декларације.
  • Провера компајлера — компајлер гарантује постојање actual имплементација за све циљне платформе.
  • Source set — сетови (iosMain, androidMain) где се налазе имплементације платформе actual.

Шта је expect/actual?

expect/actual — декларативни механизам Kotlin Multiplatform за имплементацију програмирања оријентисаног на платформу. Омогућава описивање АПИ-ја једном у заједничком модулу (expect) и његову имплементацију одвојено за сваку платформу (actual). За разлику од интерфејса, expect/actual не ствара виртуелне позиве — компајлер повезује expect и actual декларације у фази компилације, чиме се елиминишу додатни трошкови динамичке диспечерије.

Историја expect/actual почела је појавом Kotlin Multiplatform 2017. године. Првобитно се механизам звао expect/actual declarations и био је експерименталан. У Kotlin 1.2 додате су expect анотације, а у Kotlin 1.3 expect/actual је постао стабилан за класе и функције. Постепено се механизам ширио: у Kotlin 1.6 додата је подршка за companion објекте, у Kotlin 1.7 — за enum класе, а у Kotlin 2.0 — за typealias.

Кључна карактеристика expect/actual — безбедност на нивоу компајлера. Ако је програмер додао expect декларацију у commonMain, али заборавио да пружи actual имплементацију за iOS, компајлер ће пријавити грешку. Ово спречава runtime грешке карактеристичне за приступе са рефлексијом или динамичким учитавањем кода платформе.

Како ради механизам expect/actual

Механизам expect/actual ради на нивоу source set — система модула Kotlin Multiplatform. Заједнички код доступан свим платформама налази се у commonMain source set-у. Код зависан од платформе — у iosMain, androidMain, macosMain и тако даље. Кључна реч expect у commonMain декларише АПИ, а кључна реч actual у source set-у платформе пружа имплементацију. Компајлер их повезује у фази генерисања кода, замењујући позив expect функције одговарајућом actual имплементацијом за циљну платформу.

Хијерархија source set-а у типичном KMM пројекту изгледа овако: commonMain садржи expect декларације, iosMain и androidMain садрже actual имплементације. При компилацији за iOS користи се actual из iosMain, при компилацији за Android — из androidMain. Source set-ови могу бити међупосредни (на пример, iosArm64Main за одређену архитектуру), што омогућава прецизирање имплементација за различите уређаје.

kotlin
// commonMain — expect декларација
expect fun getPlatformName(): String

// androidMain — actual за Android
actual fun getPlatformName(): String = "Android"

// iosMain — actual за iOS
actual fun getPlatformName(): String = "iOS"

Провера actual имплементација од стране компајлера

Kotlin компајлер проверава неколико услова при раду са expect/actual. Свака expect декларација мора имати actual имплементацију за сваку активну платформу. Потпис actual декларације мора се поклапати са expect потписом (@OptionalExpectation анотација може ублажити овај захтев). Модификатори приступа, повратни тип и параметри морају бити идентични. Компајлер такође проверава одсуство цикличних зависности између expect и actual декларација.

Типови expect/actual: функције, класе, особине

expect/actual подржава неколико типова декларација. Најчешће се користе expect/actual функције за операције платформе, expect/actual класе за објекте који захтевају изворну имплементацију и expect/actual особине за константе и подешавања. Сваки тип има своја правила коришћења и ограничења.

Expect/actual функције — најједноставнији и најраспрострањенији тип. Користе се за позивање АПИ-ја платформе као што су добијање времена, читање датотека или слање HTTP захтева. Expect/actual класе примењују се за креирање објеката који директно интерагују са изворним кодом (на пример, за приступ камери, геолокацији или сигурном складишту). Expect/actual особине (val) погодне су за константе платформе — назив оперативног система, верзија SDK-а или путања до системског директоријума.

Тип декларацијеКључне речиПример коришћења
Функцијаexpect fun / actual funДобијање јединственог идентификатора уређаја
Класаexpect class / actual classПриступ SecureStorage (Keychain / EncryptedSharedPreferences)
Особинаexpect val / actual valТренутна платформа (iOS / Android)
Enum класаexpect enum / actual enumЛиста доступних дозвола апликације
Typealiasexpect typealias / actual typealiasТип мрежног одговора специфичан за платформу

Ограничења expect/actual

Нису све Kotlin конструкције могуће користити са expect/actual. Expect декларација не може садржати тело — само потпис. Expect класа не може имати конструктор са параметрима (мора имати празан главни конструктор). За enum expect/actual све константе морају бити идентичне у expect и actual. Expect особине морају бити val (не var), јер чување стања у заједничком модулу за особине платформе нема смисла.

Примери кода: од једноставног до сложеног

Размотримо практичне примере expect/actual од једноставних функција до потпуних класа. Основни случај — добијање назива платформе за коришћење у интерфејсу. Сложенији примери укључују приступ изворном складишту и рад са нитима платформе.

kotlin
// commonMain — expect класа за сигурно складиште
expect class PlatformStorage {
    fun save(key: String, value: String)
    fun get(key: String): String?
    fun remove(key: String)
}

// androidMain — actual на Android-у
actual class PlatformStorage {
    private val prefs = AppContext.getSharedPreferences("secure", 0)

    actual fun save(key: String, value: String) { prefs.edit().putString(key, value).apply() }
    actual fun get(key: String): String? = prefs.getString(key, null)
    actual fun remove(key: String) { prefs.edit().remove(key).apply() }
}

У овом примеру, expect класа PlatformStorage дефинише уговор једноставног складишта кључ-вредност. На Android-у имплементација користи SharedPreferences, а на iOS-у — Keychain или NSUserDefaults. Захваљујући expect/actual, пословна логика у commonMain позива save/get/remove без познавања имплементације платформе.

kotlin
// iosMain — actual на iOS-у са Keychain
actual class PlatformStorage {
    actual fun save(key: String, value: String) {
        val query = mapOf<String, Any>(
            kSecClass to kSecClassGenericPassword,
            kSecAttrAccount to key,
            kSecValueData to value.encodeToByteArray()
        )
        SecItemAdd(query, null)
    }

    actual fun get(key: String): String? {
        val query = mapOf<String, Any>(
            kSecClass to kSecClassGenericPassword,
            kSecAttrAccount to key,
            kSecReturnData to true
        )
        val result = mutableMapOf<String, Any>()
        return if (SecItemCopyMatching(query, result) == errSecSuccess)
            result[kSecValueData]?.toString()
        else null
    }

    actual fun remove(key: String) {
        val query = mapOf<String, Any>(
            kSecClass to kSecClassGenericPassword,
            kSecAttrAccount to key
        )
        SecItemDelete(query)
    }
}

Најбоље праксе expect/actual

При пројектовању expect/actual АПИ-ја треба се придржавати неколико принципа. Минимизирајте број expect декларација — што је више заједничког кода, то је одржавање лакше. Користите expect/actual само за АПИ-је који се стварно разликују на платформама. За остали код примењујте интерфејсе са фабрикама или убризгавањем зависности, што поједностављује тестирање.

Препоручује се груписање expect декларација по тематским модулима, а не мешање у истом фајлу. На пример, Storage.kt за expect декларације складишта, Platform.kt за expect функције рада са оперативним системом и Analytics.kt за expect класе аналитике. Ово поједностављује навигацију и разумевање површине платформе KMM пројекта. Сваки actual фајл треба да се налази у одговарајућем source set-у: androidMain, iosMain, desktopMain и тако даље.

Подразумеване имплементације преко expect fun са actual fun, где actual користи заједнички код — распрострањени анти-образац. Ако се имплементација платформе не разликује од подразумеване, expect/actual није потребан. У таквим случајевима користите једноставну функцију у commonMain. Такође избегавајте expect/actual за тривијалне гетере — користите expect val са константама.

Организација кода у пројекту

Правилна структура expect/actual кода је критична за читљивост пројекта. Сваки expect/actual модул треба да има јединствену улазну тачку. Пример организације: commonMain/kotlin/com/project/platform садржи expect декларације, androidMain/kotlin/com/project/platform — actual за Android, iosMain/kotlin/com/project/platform — actual за iOS. Називи фајлова и пакета треба да се поклапају за expect и actual, како би програмер могао брзо пронаћи одговарајућу имплементацију.

Алтернативе expect/actual у KMM

Интерфејси са фабриком платформе — главна алтернатива expect/actual. Уместо expect класе може се декларисати интерфејс у commonMain, а конкретне класе креирати у модулима платформе. Фабрика или контејнер за убризгавање зависности пружају исправну имплементацију у runtime-у. Овај приступ је бољи за тестирање јер се интерфејс може имитирати.

Убризгавање зависности (Koin, Kodein) — флексибилнији, али мање перформантан приступ. DI контејнер се конфигурише одвојено за сваку платформу и пружа зависности платформе заједничком коду. За разлику од expect/actual, убризгавање се дешава у runtime-у, што омогућава замену имплементација за тестирање. С друге стране, грешке конфигурације DI откривају се тек при покретању, а не у фази компилације.

ПриступПровера у фази компилацијеФлексибилност тестирањаRuntime додатни трошкови
expect/actualПотпунаНиска (actual се не може имитирати)Нулти (везивање у компилацији)
Интерфејси + фабрикаДелимичнаВисока (може се имитирати)Минимални (виртуелни позив)
Убризгавање зависностиНе (runtime)ВисокаСредњи (DI прокси)

Избор између expect/actual и алтернатива зависи од контекста. За критичне перформансе (погони игара, обрада у реалном времену) expect/actual је пожељнији због нултих runtime трошкова. За пословну логику (репозиторијуми, use-case-ови) боље је користити интерфејсе са DI како би се поједноставило тестирање. Комбиновани приступ — expect/actual за нискoнивoске операције платформе и интерфејси за слој пословне логике — примењује се у већини производних KMM пројеката.

Често постављана питања

Која је разлика између expect/actual и интерфејса?

expect/actual повезује имплементацију у фази компилације без виртуелних позива, а интерфејси — у runtime-у. expect/actual гарантује постојање имплементације за све платформе, интерфејси захтевају runtime провере.

Може ли се expect/actual користити за enum?

Да, expect enum је подржан од Kotlin 1.7. Све константе у expect и actual enum морају се поклапати. Различите вредности константи на различитим платформама — грешка компилације.

Шта се дешава ако се заборави actual имплементација?

Компајлер ће пријавити грешку за сваку платформу где недостаје actual имплементација. Пројекат се неће изградити док се за све expect декларације не додају одговарајуће actual имплементације.

Може ли се expect/actual користити унутар једног source set-а?

Не, expect и actual морају бити у различитим source set-овима. expect — у commonMain или међупосредном source set-у, actual — у source set-у платформе. Постављање expect и actual у исти source set је грешка компилације.

Како тестирати expect/actual код?

За тестирање expect/actual користите commonTest са source set-овима за тестирање платформе. Напишите expect тестове у commonTest и actual тестове за сваку платформу. Интеграциони тестови се покрећу одвојено на свакој циљној платформи.

Резиме

  • expect/actual — кључни механизам Kotlin Multiplatform за имплементације платформе са провером компајлера.
  • expect декларише уговор у commonMain, actual пружа имплементацију у source set-у платформе.
  • Типови декларација укључују функције, класе, особине, enum класе и typealias са различитим правилима коришћења.
  • Провера компајлера гарантује постојање actual имплементација за све циљне платформе, спречавајући runtime грешке.
  • Препоручује се минимизирање expect/actual и коришћење интерфејса са DI за пословну логику.
  • Организација кода треба да буде једнообразна са поклапајућим називима фајлова и пакета за expect и actual.
  • Користите expect/actual за нискoнивoске операције платформе (складиште, систем датотека, сензори) — ово обезбеђује нулте runtime трошкове.

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође