expect/actual — podstata, klíčová slova KMM a jak fungují

Autor: IT Sectr Publikováno: 2026-06-05 Doba čtení: 8 min

expect/actual — mechanismus Kotlin Multiplatform, který umožňuje deklarovat platformově závislá API ve společném kódu. Klíčové slovo expect vytváří kontrakt funkce, třídy nebo vlastnosti v commonMain, zatímco klíčové slovo actual poskytuje konkrétní implementaci pro každou platformu. Kompilátor kontroluje, že každé expect-deklaraci odpovídá actual-implementace na všech cílových platformách. Podle údajů JetBrains, 2025 se mechanismus používá v 80 % KMM-projektů pro implementaci platformové obchodní logiky.

Hlavní body

  • expect — klíčové slovo pro deklaraci kontraktu funkce, třídy nebo vlastnosti ve společném kódu.
  • actual — klíčové slovo pro poskytnutí platformové implementace expect-deklarace.
  • commonMain — source set se společným kódem, kde jsou umístěny expect-deklarace.
  • Kontrola kompilátorem — kompilátor garantuje existenci actual-implementací pro všechny cílové platformy.
  • Source set — sady (iosMain, androidMain), kde jsou umístěny platformové actual-implementace.

Co je expect/actual?

expect/actual — je deklarativní mechanismus Kotlin Multiplatform pro implementaci platformově orientovaného programování. Umožňuje popsat API jednou ve společném modulu (expect) a implementovat jej zvlášť pro každou platformu (actual). Na rozdíl od rozhraní expect/actual nevytváří virtuální volání — kompilátor spojuje expect- a actual-deklarace ve fázi kompilace, což vylučuje režii dynamického směrování.

Historie expect/actual začala s příchodem Kotlin Multiplatform v roce 2017. Původně se mechanismus nazýval expect/actual declarations a byl experimentální. V Kotlin 1.2 byly přidány expect-anotace a v Kotlin 1.3 se expect/actual stal stabilním pro třídy a funkce. Postupně se mechanismus rozšiřoval: v Kotlin 1.6 byla přidána podpora expect/actual pro companion-objekty, v Kotlin 1.7 — pro enum-třídy a v Kotlin 2.0 — pro typealias.

Klíčovou vlastností expect/actual je bezpečnost na úrovni kompilace. Pokud vývojář přidá expect-deklaraci do commonMain, ale zapomene poskytnout actual-implementaci pro iOS, kompilátor vydá chybu. To zabraňuje runtime-chybám typickým pro přístupy s reflexí nebo dynamickým načítáním platformového kódu.

Jak funguje mechanismus expect/actual

Mechanismus expect/actual pracuje na úrovni source set — systém modulů Kotlin Multiplatform. Společný kód přístupný všem platformám je umístěn v source setu commonMain. Platformově závislý kód — v iosMain, androidMain, macosMain atd. Klíčové slovo expect v commonMain deklaruje API, zatímco klíčové slovo actual v platformovém source setu poskytuje implementaci. Kompilátor je spojuje ve fázi generování kódu, nahrazuje volání expect-funkce odpovídající actual-implementací pro cílovou platformu.

Hierarchie source set v typickém KMM-projektu vypadá následovně: commonMain obsahuje expect-deklarace, iosMain a androidMain obsahují actual-implementace. Při kompilaci pro iOS se používá actual z iosMain, při kompilaci pro Android — z androidMain. Source sety mohou být střední (např. iosArm64Main pro konkrétní architekturu), což umožňuje upřesnit implementace pro různá zařízení.

kotlin
// commonMain — deklarace expect
expect fun getPlatformName(): String

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

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

Kompilátorová kontrola actual-implementací

Kompilátor Kotlin kontroluje několik podmínek při práci s expect/actual. Každá expect-deklarace musí mít actual-implementaci pro každou aktivní platformu. Signatura actual-deklarace musí odpovídat expect-signatuře (anotace @OptionalExpectation může toto požadavky zmírnit). Modifikátory přístupu, návratový typ a parametry musí být identické. Kompilátor také kontroluje nepřítomnost cyklických závislostí mezi expect- a actual-deklaracemi.

Typy expect/actual: funkce, třídy, vlastnosti

expect/actual podporuje několik typů deklarací. Nejčastěji se používají expect/actual-funkce pro platformové operace, expect/actual-třídy pro objekty vyžadující nativní implementaci a expect/actual-vlastnosti pro konstanty a nastavení. Každý typ má svá pravidla použití a omezení.

Expect/actual-funkce — nejjednodušší a nejrozšířenější typ. Používají se pro volání platformových API, jako je získání času, čtení souborů nebo odesílání HTTP požadavků. Expect/actual-třídy se používají pro vytváření objektů, které přímo interagují s nativním kódem (např. pro přístup ke kameře, geolokaci nebo bezpečnému úložišti). Expect/actual-vlastnosti (val) jsou vhodné pro platformové konstanty — název OS, verzi SDK nebo cestu k systémovému adresáři.

Typ deklaraceKlíčová slovaPříklad použití
Funkceexpect fun / actual funZískání unikátního identifikátoru zařízení
Třídaexpect class / actual classPřístup k SecureStorage (Keychain / EncryptedSharedPreferences)
Vlastnostexpect val / actual valAktuální platforma (iOS / Android)
Enum-třídaexpect enum / actual enumSeznam dostupných oprávnění aplikace
Typealiasexpect typealias / actual typealiasTyp síťové odpovědi specifický pro platformu

Omezení expect/actual

Ne všechny konstrukce Kotlin lze použít s expect/actual. Expect-deklarace nemůže obsahovat tělo — pouze signaturu. Expect-třída nemůže mít konstruktor s parametry (musí mít prázdný primary constructor). Pro enum expect/actual musí být všechny konstanty stejné v expect i actual. Expect-vlastnosti musí být val (ne var), protože ukládání stavu ve společném modulu pro platformové vlastnosti nedává smysl.

Příklady kódu: od jednoduchého po složité

Podívejme se na praktické příklady expect/actual od jednoduchých funkcí až po plnohodnotné třídy. Základní případ — získání názvu platformy pro použití v UI. Složitější příklady zahrnují přístup k nativnímu úložišti a práci s platformovými vlákny.

kotlin
// commonMain — expect třída pro bezpečné úložiště
expect class PlatformStorage {
    fun save(key: String, value: String)
    fun get(key: String): String?
    fun remove(key: String)
}

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

V tomto příkladu expect-třída PlatformStorage definuje kontrakt jednoduchého úložiště klíč-hodnota. Na Androidu implementace používá SharedPreferences, na iOS — Keychain nebo NSUserDefaults. Díky expect/actual volá obchodní logika v commonMain save/get/remove, aniž by věděla o platformové implementaci.

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

Nejlepší postupy expect/actual

Při navrhování expect/actual API byste se měli řídit několika zásadami. Minimalizujte počet expect-deklarací — čím více společného kódu, tím snazší údržba. Používejte expect/actual pouze pro ta API, která se na platformách skutečně liší. Pro zbytek kódu používejte rozhraní s továrnami nebo dependency injection, což zjednodušuje testování.

Doporučuje se seskupovat expect-deklarace do tematických modulů, nikoli je míchat v jednom souboru. Například Storage.kt pro expect-deklarace úložiště, Platform.kt pro expect-funkce práce s OS a Analytics.kt pro expect-třídy analýzy. To zjednodušuje navigaci a pochopení platformového povrchu KMM-projektu. Každý actual-soubor by měl být v odpovídajícím source setu: androidMain, iosMain, desktopMain atd.

Výchozí implementace pomocí expect fun s actual fun, kde actual používá společný kód, je běžný anti-vzor. Pokud se platformová implementace neliší od výchozí, expect/actual není potřeba. V takových případech použijte jednoduchou funkci v commonMain. Také se vyhněte expect/actual pro triviální gettery — použijte expect val s konstantami.

Organizace kódu v projektu

Správná struktura expect/actual kódu je kritická pro čitelnost projektu. Každý expect/actual modul by měl mít jediný vstupní bod. Příklad organizace: commonMain/kotlin/com/project/platform obsahuje expect-deklarace, androidMain/kotlin/com/project/platform — actual pro Android, iosMain/kotlin/com/project/platform — actual pro iOS. Názvy souborů a balíčků by měly odpovídat pro expect i actual, aby vývojář rychle našel odpovídající implementaci.

Alternativy k expect/actual v KMM

Rozhraní s platformovou továrnou — hlavní alternativa k expect/actual. Místo expect-třídy můžete deklarovat rozhraní v commonMain a vytvořit konkrétní třídy v platformových modulech. Továrna nebo dependency injection kontejner poskytuje správnou implementaci za běhu. Tento přístup je vhodnější pro testování, protože rozhraní lze mockovat.

Dependency Injection (Koin, Kodein) — flexibilnější, ale méně výkonný přístup. DI-kontejner se nastavuje zvlášť pro každou platformu a poskytuje platformové závislosti do společného kódu. Na rozdíl od expect/actual probíhá injekce za běhu, což umožňuje nahrazovat implementace pro testování. Nevýhodou je, že chyby konfigurace DI se odhalí až při spuštění, nikoli při kompilaci.

PřístupKontrola při kompilaciFlexibilita testováníRežie za běhu
expect/actualÚplnáNízká (actual nelze mockovat)Žádná (kompilační vazba)
Rozhraní + továrnaČástečnáVysoká (lze mockovat)Minimální (virtuální volání)
Dependency InjectionNe (runtime)VysokáStřední (DI-proxy)

Volba mezi expect/actual a alternativami závisí na kontextu. Pro kritický výkon (herní enginy, zpracování v reálném čase) je expect/actual výhodnější díky nulové režii. Pro obchodní logiku (repozitáře, use-cases) je lepší použít rozhraní s DI pro zjednodušení testování. Kombinovaný přístup — expect/actual pro nízkoúrovňové platformové operace a rozhraní pro vrstvu obchodní logiky — se používá ve většině produkčních KMM-projektů.

Často kladené otázky

Jaký je rozdíl mezi expect/actual a rozhraními?

expect/actual spojuje implementaci ve fázi kompilace bez virtuálních volání, zatímco rozhraní — za běhu. expect/actual garantuje existenci implementace pro všechny platformy, rozhraní vyžadují runtime-kontroly.

Lze použít expect/actual pro enum?

Ano, expect enum je podporován od Kotlin 1.7. Všechny konstanty v expect a actual enum musí být shodné. Různé hodnoty konstant na různých platformách jsou chybou kompilace.

Co se stane, když zapomenu actual-implementaci?

Kompilátor vydá chybu pro každou platformu, kde actual-implementace chybí. Projekt se nesestaví, dokud nebudou pro všechny expect-deklarace přidány odpovídající actual-implementace.

Lze použít expect/actual v rámci jednoho source setu?

Ne, expect a actual musí být v různých source setech. expect — v commonMain nebo středním source setu, actual — v platformovém source setu. Umístění expect a actual ve stejném source setu je chybou kompilace.

Jak testovat expect/actual kód?

Pro testování expect/actual použijte commonTest s platformovými testovacími source sety. Napište expect-testy v commonTest a actual-testy pro každou platformu. Integrační testy se spouštějí zvlášť na každé cílové platformě.

Shrnutí

  • expect/actual — klíčový mechanismus Kotlin Multiplatform pro platformové implementace s kontrolou kompilátorem.
  • expect deklaruje kontrakt v commonMain, actual poskytuje implementaci v platformovém source setu.
  • Typy deklarací zahrnují funkce, třídy, vlastnosti, enum-třídy a typealias s různými pravidly použití.
  • Kontrola kompilátorem garantuje existenci actual-implementací pro všechny cílové platformy, čímž předchází runtime-chybám.
  • Doporučuje se minimalizovat expect/actual a používat rozhraní s DI pro obchodní logiku.
  • Organizace kódu by měla být jednotná s odpovídajícími názvy souborů a balíčků pro expect a actual.
  • Používejte expect/actual pro nízkoúrovňové platformové operace (úložiště, souborový systém, senzory) — zajišťuje nulovou režii za běhu.

Vyvineme mobilní aplikaci na klíč

IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.

Prodiskutovat projekt

Přečtěte si také