expect/actual — esența, cuvintele cheie KMM și cum funcționează

Autor: IT Sectr Publicat: 2026-06-05 Timp de citire: 8 min

expect/actual — mecanismul Kotlin Multiplatform care permite declararea API-urilor dependente de platformă în codul comun. Cuvântul cheie expect creează un contract al funcției, clasei sau proprietății în commonMain, iar cuvântul cheie actual furnizează o implementare concretă pentru fiecare platformă. Compilatorul verifică dacă fiecărei declarații expect îi corespunde o implementare actual pe toate platformele țintă. Potrivit JetBrains, 2025, mecanismul este utilizat în 80% din proiectele KMM pentru implementarea logicii de afaceri specifice platformei.

Principalele

  • expect — cuvânt cheie pentru declararea contractului unei funcții, clase sau proprietăți în codul comun.
  • actual — cuvânt cheie pentru furnizarea implementării de platformă a declarației expect.
  • commonMain — source set cu codul comun unde se află declarațiile expect.
  • Verificarea compilatorului — compilatorul garantează existența implementărilor actual pentru toate platformele țintă.
  • Source set — seturi (iosMain, androidMain) unde se află implementările de platformă actual.

Ce este expect/actual?

expect/actual — este un mecanism declarativ Kotlin Multiplatform pentru implementarea programării orientate pe platformă. Acesta permite descrierea API-ului o dată în modulul comun (expect) și implementarea lui separat pentru fiecare platformă (actual). Spre deosebire de interfețe, expect/actual nu creează apeluri virtuale — compilatorul leagă declarațiile expect și actual în faza de compilare, ceea ce elimină costurile suplimentare ale expedierii dinamice.

Istoria expect/actual a început odată cu apariția Kotlin Multiplatform în 2017. Inițial mecanismul se numea expect/actual declarations și era experimental. În Kotlin 1.2 au fost adăugate adnotările expect, iar în Kotlin 1.3 expect/actual a devenit stabil pentru clase și funcții. Treptat mecanismul s-a extins: în Kotlin 1.6 a fost adăugat suportul pentru obiecte companion, în Kotlin 1.7 — pentru clase enum, iar în Kotlin 2.0 — pentru typealias.

Caracteristica cheie a expect/actual — siguranța la nivel de compilator. Dacă dezvoltatorul a adăugat o declarație expect în commonMain dar a uitat să furnizeze o implementare actual pentru iOS, compilatorul va da o eroare. Aceasta previne erorile de runtime caracteristice abordărilor cu反射 sau încărcare dinamică a codului de platformă.

Cum funcționează mecanismul expect/actual

Mecanismul expect/actual funcționează la nivel de source set — sistemul de module Kotlin Multiplatform. Codul comun accesibil tuturor platformelor se află în source set-ul commonMain. Codul dependent de platformă — în iosMain, androidMain, macosMain și așa mai departe. Cuvântul cheie expect în commonMain declară API-ul, iar cuvântul cheie actual în source set-ul de platformă furnizează implementarea. Compilatorul le leagă în faza de generare a codului, înlocuind apelul funcției expect cu implementarea actual corespunzătoare pentru platforma țintă.

Ierarhia source set într-un proiect KMM tipic arată astfel: commonMain conține declarațiile expect, iosMain și androidMain conțin implementările actual. La compilarea pentru iOS se utilizează actual din iosMain, la compilarea pentru Android — din androidMain. Source set-urile pot fi intermediare (de exemplu, iosArm64Main pentru o arhitectură specifică), ceea ce permite rafinarea implementărilor pentru diferite dispozitive.

kotlin
// commonMain — declarație expect
expect fun getPlatformName(): String

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

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

Verificarea implementărilor actual de către compilator

Compilatorul Kotlin verifică mai multe condiții la lucrul cu expect/actual. Fiecare declarație expect trebuie să aibă o implementare actual pentru fiecare platformă activă. Semnătura declarației actual trebuie să coincidă cu semnătura expect (adnotarea @OptionalExpectation poate atenua această cerință). Modificatorii de acces, tipul de returnare și parametrii trebuie să fie identici. Compilatorul verifică de asemenea absența dependențelor ciclice între declarațiile expect și actual.

Tipuri expect/actual: funcții, clase, proprietăți

expect/actual suportă mai multe tipuri de declarații. Cele mai frecvent utilizate sunt funcțiile expect/actual pentru operații de platformă, clasele expect/actual pentru obiecte care necesită implementare nativă și proprietățile expect/actual pentru constante și setări. Fiecare tip are propriile reguli de utilizare și limitări.

Funcțiile expect/actual — cel mai simplu și răspândit tip. Ele sunt utilizate pentru apelarea API-urilor de platformă precum obținerea timpului, citirea fișierelor sau trimiterea cererilor HTTP. Clasele expect/actual se aplică pentru crearea obiectelor care interacționează direct cu codul nativ (de exemplu, pentru accesul la cameră, geolocație sau stocare securizată). Proprietățile expect/actual (val) sunt potrivite pentru constante de platformă — numele sistemului de operare, versiunea SDK sau calea către directorul de sistem.

Tip declarațieCuvinte cheieExemplu de utilizare
Funcțieexpect fun / actual funObținerea identificatorului unic al dispozitivului
Clasăexpect class / actual classAcces la SecureStorage (Keychain / EncryptedSharedPreferences)
Proprietateexpect val / actual valPlatforma curentă (iOS / Android)
Clasă enumexpect enum / actual enumLista permisiunilor disponibile ale aplicației
Typealiasexpect typealias / actual typealiasTipul răspunsului de rețea specific platformei

Limitări expect/actual

Nu toate construcțiile Kotlin pot fi utilizate cu expect/actual. Declarația expect nu poate conține corp — doar semnătură. Clasa expect nu poate avea un constructor cu parametri (trebuie să aibă un constructor principal gol). Pentru enum expect/actual toate constantele trebuie să fie identice în expect și actual. Proprietățile expect trebuie să fie val (nu var), deoarece stocarea stării în modulul comun pentru proprietățile de platformă nu are sens.

Exemple de cod: de la simplu la complex

Să analizăm exemple practice expect/actual de la funcții simple la clase complete. Cazul de bază — obținerea numelui platformei pentru utilizare în interfață. Exemple mai complexe includ accesul la stocarea nativă și lucrul cu firele de execuție de platformă.

kotlin
// commonMain — clasă expect pentru stocare securizată
expect class PlatformStorage {
    fun save(key: String, value: String)
    fun get(key: String): String?
    fun remove(key: String)
}

// androidMain — actual pe 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() }
}

În acest exemplu, clasa expect PlatformStorage definește contractul unui stocare simplă cheie-valoare. Pe Android implementarea utilizează SharedPreferences, iar pe iOS — Keychain sau NSUserDefaults. Datorită expect/actual, logica de afaceri din commonMain apelează save/get/remove fără a cunoaște implementarea de platformă.

kotlin
// iosMain — actual pe iOS cu 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)
    }
}

Cele mai bune practici expect/actual

La proiectarea API-ului expect/actual trebuie respectate câteva principii. Minimizați numărul de declarații expect — cu cât mai mult cod comun, cu atât întreținerea este mai ușoară. Utilizați expect/actual doar pentru API-urile care chiar diferă pe platforme. Pentru restul codului aplicați interfețe cu fabrici sau injectare de dependență, ceea ce simplifică testarea.

Se recomandă gruparea declarațiilor expect pe module tematice, nu amestecarea lor în același fișier. De exemplu, Storage.kt pentru declarațiile expect de stocare, Platform.kt pentru funcțiile expect de lucru cu sistemul de operare și Analytics.kt pentru clasele expect de analitică. Aceasta simplifică navigarea și înțelegerea suprafeței de platformă a proiectului KMM. Fiecare fișier actual trebuie să se afle în source set-ul corespunzător: androidMain, iosMain, desktopMain și așa mai departe.

Implementările implicite prin expect fun cu actual fun, unde actual utilizează cod comun — este un anti-pattern răspândit. Dacă implementarea de platformă nu diferă de cea implicită, expect/actual nu este necesar. În astfel de cazuri utilizați o funcție simplă în commonMain. De asemenea, evitați expect/actual pentru getteri triviali — utilizați expect val cu constante.

Organizarea codului în proiect

Structura corectă a codului expect/actual este critică pentru lizibilitatea proiectului. Fiecare modul expect/actual trebuie să aibă un singur punct de intrare. Exemplu de organizare: commonMain/kotlin/com/project/platform conține declarațiile expect, androidMain/kotlin/com/project/platform — actual pentru Android, iosMain/kotlin/com/project/platform — actual pentru iOS. Numele fișierelor și pachetelor trebuie să coincidă pentru expect și actual, astfel încât dezvoltatorul să poată găsi rapid implementarea corespunzătoare.

Alternative la expect/actual în KMM

Interfețele cu fabrică de platformă — principala alternativă la expect/actual. În locul clasei expect se poate declara o interfață în commonMain, iar clasele concrete se creează în modulele de platformă. Fabrica sau containerul de injectare a dependențelor furnizează implementarea corectă în runtime. Această abordare este mai potrivită pentru testare, deoarece interfața poate fi mascată.

Injectarea dependențelor (Koin, Kodein) — o abordare mai flexibilă dar mai puțin performantă. Containerul DI este configurat separat pentru fiecare platformă și furnizează dependențele de platformă către codul comun. Spre deosebire de expect/actual, injectarea are loc în runtime, ceea ce oferă posibilitatea de a înlocui implementările pentru testare. Pe de altă parte, erorile de configurare DI sunt detectate doar la pornire, nu în faza de compilare.

AbordareVerificare în faza de compilareFlexibilitate testareCost suplimentar runtime
expect/actualCompletăScăzută (actual nu poate fi mascat)Zero (legare la compilare)
Interfețe + fabricăParțialăRidicată (poate fi mascat)Minime (apel virtual)
Injectare dependențeNu (runtime)RidicatăMedii (proxy DI)

Alegerea între expect/actual și alternative depinde de context. Pentru performanță critică (motoare de jocuri, procesare în timp real) expect/actual este preferat datorită costului zero runtime. Pentru logica de afaceri (repository-uri, use-case-uri) este mai bine să utilizați interfețe cu DI pentru a simplifica testarea. Abordarea combinată — expect/actual pentru operații de platformă de nivel scăzut și interfețe pentru stratul de logică de afaceri — se aplică în majoritatea proiectelor KMM de producție.

Întrebări frecvente

Care este diferența dintre expect/actual și interfețe?

expect/actual leagă implementarea în faza de compilare fără apeluri virtuale, iar interfețele — în runtime. expect/actual garantează existența implementării pentru toate platformele, interfețele necesită verificări în runtime.

Se poate utiliza expect/actual pentru enum?

Da, expect enum este suportat începând cu Kotlin 1.7. Toate constantele în expect și actual enum trebuie să coincidă. Valorile diferite ale constantelor pe platforme diferite — eroare de compilare.

Ce se întâmplă dacă se uită implementarea actual?

Compilatorul va da o eroare pentru fiecare platformă unde lipsește implementarea actual. Proiectul nu se va construi până când pentru toate declarațiile expect nu vor fi adăugate implementările actual corespunzătoare.

Se poate utiliza expect/actual în același source set?

Nu, expect și actual trebuie să se afle în source set-uri diferite. expect — în commonMain sau source set intermediar, actual — în source set-ul de platformă. Plasarea expect și actual în același source set este o eroare de compilare.

Cum se testează codul expect/actual?

Pentru testarea expect/actual utilizați commonTest cu source set-uri de test de platformă. Scrieți teste expect în commonTest și teste actual pentru fiecare platformă. Testele de integrare se rulează separat pe fiecare platformă țintă.

Rezumat

  • expect/actual — mecanismul cheie Kotlin Multiplatform pentru implementări de platformă cu verificare la compilare.
  • expect declară contractul în commonMain, actual furnizează implementarea în source set-ul de platformă.
  • Tipurile de declarații includ funcții, clase, proprietăți, clase enum și typealias cu diferite reguli de utilizare.
  • Verificarea compilatorului garantează existența implementărilor actual pentru toate platformele țintă, prevenind erorile de runtime.
  • Se recomandă minimizarea expect/actual și utilizarea interfețelor cu DI pentru logica de afaceri.
  • Organizarea codului trebuie să fie uniformă, cu nume de fișiere și pachete identice pentru expect și actual.
  • Utilizați expect/actual pentru operații de platformă de nivel scăzut (stocare, sistem de fișiere, senzori) — aceasta asigură cost zero runtime.

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și