expect/actual — lényeg, KMM kulcsszavak és hogyan működnek

Szerző: IT Sectr Megjelenés: 2026-06-05 Olvasási idő: 8 perc

expect/actual — a Kotlin Multiplatform mechanizmusa, amely lehetővé teszi platformfüggő API-k deklarálását a közös kódban. Az expect kulcsszó egy függvény, osztály vagy tulajdonság szerződését hozza létre a commonMain-ben, az actual kulcsszó pedig konkrét implementációt biztosít minden platformhoz. A fordító ellenőrzi, hogy minden expect deklarációhoz tartozik-e actual implementáció az összes célplatformon. A JetBrains, 2025 adatai szerint a mechanizmust a KMM-projektek 80%-ában használják platform-specifikus üzleti logika megvalósítására.

Főbb pontok

  • expect — kulcsszó egy függvény, osztály vagy tulajdonság szerződésének deklarálásához a közös kódban.
  • actual — kulcsszó az expect deklaráció platform implementációjának biztosításához.
  • commonMain — source set a közös kóddal, ahol az expect deklarációk találhatók.
  • Fordítói ellenőrzés — a fordító garantálja az actual implementációk meglétét az összes célplatformhoz.
  • Source set – halmazok (iosMain, androidMain), ahol a platform actual implementációk találhatók.

Mi az expect/actual?

expect/actual — a Kotlin Multiplatform deklaratív mechanizmusa platform-orientált programozás megvalósításához. Lehetővé teszi az API egyszeri leírását a közös modulban (expect) és különálló megvalósítását minden platformhoz (actual). Az interfészekkel ellentétben az expect/actual nem hoz létre virtuális hívásokat — a fordító összeköti az expect és actual deklarációkat a fordítási fázisban, ami kiküszöböli a dinamikus kiosztás többletterhelését.

Az expect/actual története a Kotlin Multiplatform 2017-es megjelenésével kezdődött. Kezdetben a mechanizmus expect/actual declarations néven volt ismert és kísérleti jellegű volt. A Kotlin 1.2-ben hozzáadták az expect annotációkat, a Kotlin 1.3-ban pedig az expect/actual stabil lett osztályok és függvények számára. Fokozatosan bővült a mechanizmus: a Kotlin 1.6-ban hozzáadták a companion objektumok támogatását, a Kotlin 1.7-ben — az enum osztályokét, a Kotlin 2.0-ban pedig — a typealias-okét.

Az expect/actual legfőbb jellemzője — biztonság fordítói szinten. Ha egy fejlesztő hozzáadott egy expect deklarációt a commonMain-hez, de elfelejtett actual implementációt biztosítani iOS-re, a fordító hibát jelez. Ez megakadályozza azokat a runtime hibákat, amelyek a reflexiós vagy dinamikus platformkód-betöltési megközelítésekre jellemzők.

Hogyan működik az expect/actual mechanizmus

A mechanizmus expect/actual source set szinten működik — a Kotlin Multiplatform modulrendszerében. Az összes platform számára elérhető közös kód a commonMain source set-ben található. A platformfüggő kód — az iosMain, androidMain, macosMain és így tovább. Az expect kulcsszó a commonMain-ben deklarálja az API-t, az actual kulcsszó a platform source set-ben pedig biztosítja az implementációt. A fordító összeköti őket a kódgenerálási fázisban, lecserélve az expect függvény hívását a megfelelő actual implementációra a célplatform számára.

A source set hierarchia egy tipikus KMM projektben így néz ki: commonMain tartalmazza az expect deklarációkat, iosMain és androidMain tartalmazza az actual implementációkat. iOS-re fordításkor az iosMain-ból származó actual, Androidra fordításkor pedig az androidMain-ból származó actual használatos. A source set-ek köztesek lehetnek (például iosArm64Main egy adott architektúrához), ami lehetővé teszi az implementációk finomítását különböző eszközökhöz.

kotlin
// commonMain — expect deklaráció
expect fun getPlatformName(): String

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

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

Az actual implementációk fordító általi ellenőrzése

A Kotlin fordító több feltételt ellenőriz az expect/actual használatakor. Minden expect deklarációhoz tartoznia kell actual implementációnak minden aktív platformhoz. Az actual deklaráció aláírásának meg kell egyeznie az expect aláírásával (az @OptionalExpectation annotáció enyhítheti ezt a követelményt). A hozzáférési módosítóknak, visszatérési típusnak és paramétereknek azonosnak kell lenniük. A fordítð ellenőrzi továbbá a ciklikus függőségek hiányát az expect és actual deklarációk között.

Expect/actual típusok: függvények, osztályok, tulajdonságok

expect/actual több deklarációtípust támogat. A leggyakrabban használtak az expect/actual függvények platform műveletekhez, az expect/actual osztályok natív implementációt igénylő objektumokhoz, és az expect/actual tulajdonságok konstanésokhoz és beállításokhoz. Minden típusnak megvannak a saját használati szabályai és korlátai.

Expect/actual függvények — a legegyszerűbb és legelterjedtebb típus. Platform API-k hívására használják, mint az idő lekérése, fájlok olvasása vagy HTTP kérések küldése. Expect/actual osztályok olyan objektumok létrehozására szolgálnak, amelyek közvetlenül érintkeznek natív kóddal (például kamera, geolokáció vagy kulcstároló eléréséhez). Expect/actual tulajdonságok (val) platform konstanésokhoz alkalmasak — operációs rendszer neve, SDK verzió vagy rendszerkönyvtár elérési útvonala.

Deklaráció típusaKulcsszavakHasználati példa
Függvényexpect fun / actual funEszköz egyedi azonosítójának lekérése
Osztályexpect class / actual classHozzáférés a SecureStorage-hoz (Keychain / EncryptedSharedPreferences)
Tulajdonságexpect val / actual valAktuális platform (iOS / Android)
Enum osztályexpect enum / actual enumElérhető alkalmazásengedélyek listája
Typealiasexpect typealias / actual typealiasPlatform-specifikus hálózati válasz típus

Az expect/actual korlátai

Nem minden Kotlin konstrukció használható az expect/actual-lal. Az expect deklaráció nem tartalmazhat törzset — csak aláírást. Az expect osztály nem rendelkezhet paraméteres konstruktorral (üres elsődleges konstruktorral kell rendelkeznie). Az enum expect/actual esetén minden konstanésnak azonosnak kell lennie az expect-ben és actual-ban. Az expect tulajdonságoknak val-nak kell lenniük (nem var-nak), mivel az állapot tárolása a közös modulban platform tulajdonságok esetén értelmetlen.

Kód példák: egyszerűtől a bonyolultig

Nézzük meg az expect/actual gyakorlati példáit egyszerű függvényektől a teljes osztályokig. Az alap eset — a platform név lekérése a felhasználói felületen való használathoz. Összetettebb példák tartalmazzák a natív tárolóhoz való hozzáférést és a platform szálakkal való munkát.

kotlin
// commonMain — expect osztály biztonságos tároláshoz
expect class PlatformStorage {
    fun save(key: String, value: String)
    fun get(key: String): String?
    fun remove(key: String)
}

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

Ebben a példában az PlatformStorage expect osztály egy egyszerű kulcs-érték tároló szerződését határozza meg. Androidon az implementáció SharedPreferences-t használ, iOS-en pedig Keychain-t vagy NSUserDefaults-ot. Az expect/actual-nak köszönhetően a commonMain üzleti logikája meghívja a save/get/remove-ot anélkül, hogy ismerné a platform implementációt.

kotlin
// iosMain — actual iOS-en Keychain-nel
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)
    }
}

Legjobb gyakorlatok expect/actual

Az expect/actual API tervezésekor több elvet kell követni. Minimalizálja az expect deklarációk számát — minél több a közös kód, annál könnyebb a karbantartás. Csak azokhoz az API-khoz használja az expect/actual-t, amelyek ténylegesen különböznek a platformokon. A többi kódhoz alkalmazzon interfészeket gyárakkal vagy függőséginjektálással, ami leegyszerűsíti a tesztelést.

Ajánlott az expect deklarációkat tematikus modulokba csoportosítani, ahelyett hogy egy fájlban keverné őket. Például Storage.kt a tárolás expect deklarációihoz, Platform.kt az operációs rendszerrel való munka expect függvényeihez és Analytics.kt az analitika expect osztályaihoz. Ez leegyszerűsíti a navigációt és a KMM projekt platformfelületének megértését. Minden actual fájlnak a megfelelő source set-ben kell lennie: androidMain, iosMain, desktopMain és így tovább.

Alapértelmezett implementációk expect fun révén actual fun-nal, ahol az actual közös kódot használ — ez egy gyakori antipattern. Ha a platform implementáció nem különbözik az alapértelmezettől, nincs szükség expect/actual-ra. Ilyen esetekben használjon egyszerű függvényt a commonMain-ben. Kerülje továbbá az expect/actual-t triviális getterekhez — használjon expect val-t konstanésokkal.

Kód szervezése a projektben

Az expect/actual kód helyes struktúrája kritikus a projekt olvashatósága szempontjából. Minden expect/actual modulnak egyetlen belépési ponttal kell rendelkeznie. Példa a szervezésre: commonMain/kotlin/com/project/platform tartalmazza az expect deklarációkat, androidMain/kotlin/com/project/platform — actual Androidhoz, iosMain/kotlin/com/project/platform — actual iOS-hez. A fájl- és csomagneveknek meg kell egyezniük az expect és actual esetében, hogy a fejlesztő gyorsan megtalálja a megfelelő implementációt.

Az expect/actual alternatívái a KMM-ben

Interfészek platform gyárral — az expect/actual fő alternatívája. Az expect osztály helyett deklarálható egy interfész a commonMain-ben, és konkret osztályok hozhatók létre a platform modulokban. A gyár vagy függőséginjektáló konténer biztosítja a megfelelő implementációt runtime-ban. Ez a megközelítés jobb tesztelésre, mivel az interfész mockolható.

Függőséginjektálás (Koin, Kodein) — rugalmasabb, de kevésbé teljesítményes megközelítés. A DI konténer minden platformhoz külön van konfigurálva, és platform függőségeket biztosít a közös kódhoz. Az expect/actual-lal ellentétben az injektálás runtime-ban történik, ami lehetővé teszi az implementációk cseréjét tesztelés céljából. Másrészt a DI konfigurációs hibák csak indításkor derülnek ki, nem a fordítási fázisban.

MegközelítésEllenőrzés fordítási fázisbanTesztelési rugalmasságRuntime többletterhelés
expect/actualTeljesAlacsony (actual nem mockolható)Nulla (fordítási kötés)
Interfészek + gyárRészlegesMagas (mockolható)Minimális (virtuális hívás)
FüggőséginjektálásNem (runtime)MagasKözepes (DI proxy)

Az expect/actual és alternatívái közötti választás a kontextustól függ. Kritikus teljesítményhez (játékmotorok, valós idejű feldolgozás) az expect/actual előnyösebb a nulla runtime többletterhelés miatt. Üzleti logikához (repository-k, use-case-ek) jobb interfészeket használni DI-vel a tesztelés egyszerűsítése érdekében. A kombinált megközelítés — expect/actual alacsony szintű platform műveletekhez és interfészek az üzleti logikai réteghez — a legtöbb production KMM projektben alkalmazott.

Gyakran ismételt kérdések

Mi a különbség az expect/actual és az interfészek között?

expect/actual összeköti az implementációt a fordítási fázisban virtuális hívások nélkül, az interfészek pedig runtime-ban. Az expect/actual garantálja az implementáció meglétét minden platformhoz, az interfészek runtime ellenőrzéseket igényelnek.

Használható az expect/actual enum-hoz?

Igen, az expect enum Kotlin 1.7 óta támogatott. Az expect és actual enum összes konstanésának meg kell egyeznie. Különböző értékek különböző platformokon — fordítási hiba.

Mi történik, ha elfelejtik az actual implementációt?

A fordító hibát jelez minden olyan platformhoz, ahol hiányzik az actual implementáció. A projekt nem épül fel, amíg nem adnak hozzá megfelelő actual implementációkat az összes expect deklarációhoz.

Használható az expect/actual egy source set-en belül?

Nem, expect és actual különböző source set-ekben kell lenniük. expect — a commonMain-ben vagy köztes source set-ben, actual — a platform source set-ben. Az expect és actual egy source set-be helyezése fordítási hiba.

Hogyan tesztelhető az expect/actual kód?

Az expect/actual teszteléséhez használja a commonTest-et platform teszt source set-ekkel. Írjon expect teszteket a commonTest-ben és actual teszteket minden platformhoz. Az integrációs tesztek külön futnak minden célplatformon.

Összefoglaló

  • expect/actual — a Kotlin Multiplatform fő mechanizmusa platform implementációkhoz fordítói ellenőrzéssel.
  • expect szerződést deklarál a commonMain-ben, actual implementációt biztosít a platform source set-ben.
  • A deklarációtípusok közé tartoznak a függvények, osztályok, tulajdonságok, enum osztályok és typealias különböző használati szabályokkal.
  • Fordítói ellenőrzés garantálja az actual implementációk meglétét az összes célplatformhoz, megelőzve a runtime hibákat.
  • Ajánlott minimalizálni az expect/actual használatát és interfészeket használni DI-vel az üzleti logikához.
  • A kód szervezésének egységesnek kell lennie azonos fájl és csomagnevekkel az expect és actual számára.
  • Használja az expect/actual-t alacsony szintű platform műveletekhez (tároló, fájlrendszer, érzékelők) — ez nulla runtime többletterhelést biztosít.

Kulcsrakész mobilalkalmazást fejlesztünk

Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.

Projekt megbeszélése

Olvassa el is