expect/actual — essentie, KMM-sleutelwoorden en hoe ze werken

Auteur: IT Sectr Gepubliceerd: 2026-06-05 Leestijd: 8 min

expect/actual — het Kotlin Multiplatform-mechanisme waarmee platformafhankelijke API’s in gemeenschappelijke code kunnen worden gedeclareerd. Het sleutelwoord expect creëert een contract van een functie, klasse of eigenschap in commonMain, en het sleutelwoord actual levert een concrete implementatie voor elk platform. De compiler controleert of elke expect-declaratie overeenkomt met een actual-implementatie op alle doelplatforms. Volgens JetBrains, 2025, wordt het mechanisme gebruikt in 80% van de KMM-projecten voor het implementeren van platform-specifieke bedrijfslogica.

Belangrijkste punten

  • expect — sleutelwoord voor het declareren van een contract van een functie, klasse of eigenschap in gemeenschappelijke code.
  • actual — sleutelwoord voor het leveren van de platformimplementatie van een expect-declaratie.
  • commonMain — source set met gemeenschappelijke code waar de expect-declaraties zich bevinden.
  • Compilercontrole — de compiler garandeert de aanwezigheid van actual-implementaties voor alle doelplatforms.
  • Source set — sets (iosMain, androidMain) waar de platform actual-implementaties zich bevinden.

Wat is expect/actual?

expect/actual — is een declaratief Kotlin Multiplatform-mechanisme voor platformgericht programmeren. Het maakt het mogelijk om API één keer in de gemeenschappelijke module (expect) te beschrijven en afzonderlijk voor elk platform (actual) te implementeren. In tegenstelling tot interfaces creëert expect/actual geen virtuele aanroepen — de compiler verbindt expect- en actual-declaraties in de compilatiefase, wat de overhead van dynamische dispatch elimineert.

De geschiedenis van expect/actual begon met de komst van Kotlin Multiplatform in 2017. Aanvankelijk heette het mechanisme expect/actual declarations en was het experimenteel. In Kotlin 1.2 werden expect-annotaties toegevoegd, en in Kotlin 1.3 werd expect/actual stabiel voor klassen en functies. Geleidelijk werd het mechanisme uitgebreid: in Kotlin 1.6 werd ondersteuning voor companion-objecten toegevoegd, in Kotlin 1.7 — voor enum-klassen, en in Kotlin 2.0 — voor typealias.

Het belangrijkste kenmerk van expect/actual — veiligheid op compilatieniveau. Als een ontwikkelaar een expect-declaratie aan commonMain heeft toegevoegd maar vergeet een actual-implementatie voor iOS te leveren, geeft de compiler een foutmelding. Dit voorkomt runtime-fouten die kenmerkend zijn voor benaderingen met reflectie of dynamisch laden van platformcode.

Hoe het expect/actual-mechanisme werkt

Het mechanisme expect/actual werkt op het niveau van source set — het modulesysteem van Kotlin Multiplatform. Gemeenschappelijke code die voor alle platforms toegankelijk is, bevindt zich in de source set commonMain. Platformafhankelijke code — in iosMain, androidMain, macosMain enzovoort. Het sleutelwoord expect in commonMain declareert de API, en het sleutelwoord actual in de platform source set levert de implementatie. De compiler verbindt ze in de codegeneratiefase, waarbij de aanroep van de expect-functie wordt vervangen door de overeenkomstige actual-implementatie voor het doelplatform.

De source set-hiërarchie in een typisch KMM-project ziet er als volgt uit: commonMain bevat de expect-declaraties, iosMain en androidMain bevatten de actual-implementaties. Bij compilatie voor iOS wordt actual uit iosMain gebruikt, bij compilatie voor Android — uit androidMain. Source sets kunnen intermediair zijn (bijvoorbeeld iosArm64Main voor een specifieke architectuur), wat het mogelijk maakt om implementaties voor verschillende apparaten te verfijnen.

kotlin
// commonMain — expect-declaratie
expect fun getPlatformName(): String

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

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

Compilercontrole van actual-implementaties

De Kotlin-compiler controleert verschillende voorwaarden bij het werken met expect/actual. Elke expect-declaratie moet een actual-implementatie hebben voor elk actief platform. De handtekening van de actual-declaratie moet overeenkomen met de expect-handtekening (de @OptionalExpectation-annotatie kan deze vereiste versoepelen). Toegangsmodifiers, retourtype en parameters moeten identiek zijn. De compiler controleert ook de afwezigheid van cyclische afhankelijkheden tussen expect- en actual-declaraties.

Typen expect/actual: functies, klassen, eigenschappen

expect/actual ondersteunt verschillende typen declaraties. De meest gebruikte zijn expect/actual-functies voor platformbewerkingen, expect/actual-klassen voor objecten die een native implementatie vereisen, en expect/actual-eigenschappen voor constanten en instellingen. Elk type heeft zijn eigen gebruiksregels en beperkingen.

Expect/actual-functies — het eenvoudigste en meest voorkomende type. Ze worden gebruikt voor het aanroepen van platform-API’s zoals het ophalen van tijd, het lezen van bestanden of het verzenden van HTTP-verzoeken. Expect/actual-klassen worden toegepast voor het maken van objecten die rechtstreeks met native code interageren (bijvoorbeeld voor toegang tot camera, geolocatie of sleutelopslag). Expect/actual-eigenschappen (val) zijn geschikt voor platformconstanten — besturingssysteemnaam, SDK-versie of pad naar de systeemmap.

DeclaratietypeSleutelwoordenVoorbeeld gebruik
Functieexpect fun / actual funUnieke apparaat-ID ophalen
Klasseexpect class / actual classToegang tot SecureStorage (Keychain / EncryptedSharedPreferences)
Eigenschapexpect val / actual valHuidig platform (iOS / Android)
Enum-klasseexpect enum / actual enumLijst met beschikbare app-machtigingen
Typealiasexpect typealias / actual typealiasPlatform-specifiek netwerkantwoordtype

Beperkingen van expect/actual

Niet alle Kotlin-constructies kunnen met expect/actual worden gebruikt. Een expect-declaratie mag geen body bevatten — alleen een handtekening. Een expect-klasse mag geen constructor met parameters hebben (moet een lege primary constructor hebben). Voor enum expect/actual moeten alle constanten identiek zijn in expect en actual. Expect-eigenschappen moeten val zijn (niet var), omdat het opslaan van status in de gemeenschappelijke module voor platformeigenschappen geen zin heeft.

Codevoorbeelden: van eenvoudig tot complex

Laten we praktische voorbeelden van expect/actual bekijken, van eenvoudige functies tot volwaardige klassen. Het basisgeval — het ophalen van de platformnaam voor gebruik in de UI. Complexere voorbeelden omvatten toegang tot native opslag en werken met platform threads.

kotlin
// commonMain — expect-klasse voor veilige opslag
expect class PlatformStorage {
    fun save(key: String, value: String)
    fun get(key: String): String?
    fun remove(key: String)
}

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

In dit voorbeeld definieert de expect-klasse PlatformStorage het contract van een eenvoudige sleutel-waardeopslag. Op Android gebruikt de implementatie SharedPreferences, en op iOS — Keychain of NSUserDefaults. Dankzij expect/actual roept de bedrijfslogica in commonMain save/get/remove aan zonder kennis van de platformimplementatie.

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

Beste praktijken voor expect/actual

Bij het ontwerpen van een expect/actual API moeten verschillende principes worden gevolgd. Minimaliseer het aantal expect-declaraties — hoe meer gemeenschappelijke code, hoe eenvoudiger het onderhoud. Gebruik expect/actual alleen voor API’s die echt verschillen tussen platforms. Voor de rest van de code pas je interfaces met fabrieken of dependency injection toe, wat testen vereenvoudigt.

Het wordt aanbevolen om expect-declaraties te groeperen op thematische modules, in plaats van ze in één bestand te mengen. Bijvoorbeeld Storage.kt voor expect-declaraties van opslag, Platform.kt voor expect-functies voor het werken met het besturingssysteem en Analytics.kt voor expect-klassen voor analyse. Dit vereenvoudigt navigatie en begrip van het platformoppervlak van het KMM-project. Elk actual-bestand moet zich in de overeenkomstige source set bevinden: androidMain, iosMain, desktopMain, enzovoort.

Standaardimplementaties via expect fun met actual fun, waarbij actual gemeenschappelijke code gebruikt — is een veelvoorkomend antipatroon. Als de platformimplementatie niet verschilt van de standaard, is expect/actual niet nodig. Gebruik in dergelijke gevallen een eenvoudige functie in commonMain. Vermijd ook expect/actual voor triviale getters — gebruik expect val met constanten.

Codeorganisatie in het project

De juiste structuur van expect/actual-code is cruciaal voor de leesbaarheid van het project. Elke expect/actual-module moet één enkel toegangspunt hebben. Voorbeeld van organisatie: commonMain/kotlin/com/project/platform bevat expect-declaraties, androidMain/kotlin/com/project/platform — actual voor Android, iosMain/kotlin/com/project/platform — actual voor iOS. Bestands- en pakketnamen moeten overeenkomen voor expect en actual, zodat de ontwikkelaar snel de bijbehorende implementatie kan vinden.

Alternatieven voor expect/actual in KMM

Interfaces met een platformfabriek — het belangrijkste alternatief voor expect/actual. In plaats van een expect-klasse kan een interface in commonMain worden gedeclareerd en concrete klassen in platformmodules worden gemaakt. De fabriek of dependency injection-container levert de juiste implementatie in runtime. Deze benadering is beter geschikt voor testen, omdat de interface kan worden gemockt.

Dependency Injection (Koin, Kodein) — een flexibelere maar minder performante benadering. De DI-container wordt afzonderlijk voor elk platform geconfigureerd en levert platformafhankelijkheden aan de gemeenschappelijke code. In tegenstelling tot expect/actual vindt injectie plaats in runtime, wat het mogelijk maakt om implementaties voor testen te vervangen. Aan de andere kant worden DI-configuratiefouten pas bij het opstarten ontdekt, niet in de compilatiefase.

BenaderingControle in compilatiefaseTestflexibiliteitRuntime-overhead
expect/actualVolledigLaag (actual kan niet worden gemockt)Geen (compilatiebinding)
Interfaces + fabriekGedeeltelijkHoog (kan worden gemockt)Minimaal (virtuele aanroep)
Dependency InjectionNee (runtime)HoogGemiddeld (DI-proxy)

De keuze tussen expect/actual en alternatieven hangt af van de context. Voor kritieke prestaties (spelmotoren, real-time verwerking) heeft expect/actual de voorkeur vanwege nul runtime-overhead. Voor bedrijfslogica (repositories, use-cases) kun je beter interfaces met DI gebruiken om het testen te vereenvoudigen. Een gecombineerde benadering — expect/actual voor laag-niveau platformbewerkingen en interfaces voor de bedrijfslogicalaag — wordt in de meeste productie-KMM-projecten toegepast.

Veelgestelde vragen

Wat is het verschil tussen expect/actual en interfaces?

expect/actual verbindt de implementatie in de compilatiefase zonder virtuele aanroepen, interfaces — in runtime. expect/actual garandeert de aanwezigheid van implementatie voor alle platforms, interfaces vereisen runtime-controles.

Kan expect/actual worden gebruikt voor enum?

Ja, expect enum wordt ondersteund vanaf Kotlin 1.7. Alle constanten in expect en actual enum moeten overeenkomen. Verschillende waarden van constanten op verschillende platforms — compilatiefout.

Wat gebeurt er als de actual-implementatie wordt vergeten?

De compiler geeft een foutmelding voor elk platform waar de actual-implementatie ontbreekt. Het project wordt niet gebouwd totdat voor alle expect-declaraties de bijbehorende actual-implementaties zijn toegevoegd.

Kan expect/actual binnen één source set worden gebruikt?

Nee, expect en actual moeten in verschillende source sets staan. expect — in commonMain of een intermediaire source set, actual — in de platform source set. Het plaatsen van expect en actual in dezelfde source set is een compilatiefout.

Hoe test je expect/actual-code?

Gebruik voor het testen van expect/actual commonTest met platform test source sets. Schrijf expect-tests in commonTest en actual-tests voor elk platform. Integratietests worden afzonderlijk op elk doelplatform uitgevoerd.

Samenvatting

  • expect/actual — het belangrijkste Kotlin Multiplatform-mechanisme voor platformimplementaties met compilercontrole.
  • expect declareert het contract in commonMain, actual levert de implementatie in de platform source set.
  • Declaratietypen omvatten functies, klassen, eigenschappen, enum-klassen en typealias met verschillende gebruiksregels.
  • Compilercontrole garandeert de aanwezigheid van actual-implementaties voor alle doelplatforms, waardoor runtime-fouten worden voorkomen.
  • Het wordt aanbevolen om expect/actual te minimaliseren en interfaces met DI te gebruiken voor bedrijfslogica.
  • Codeorganisatie moet uniform zijn met overeenkomende bestands- en pakketnamen voor expect en actual.
  • Gebruik expect/actual voor laag-niveau platformbewerkingen (opslag, bestandssysteem, sensoren) — dit garandeert nul runtime-overhead.

We ontwikkelen een mobiele applicatie turnkey

IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.

Bespreek het project

Lees ook