Kotlin Multiplatform 2026: code delen tussen Android en iOS
Hoe Kotlin Multiplatform in 2026 bedrijfslogica deelt tussen Android en iOS: Gradle-setup, expect/actual, Ktor, Compose Multiplatform en KMP-sollicitatievragen.

Kotlin Multiplatform (KMP) laat één Kotlin-codebase de bedrijfslogica van zowel Android- als iOS-apps aandrijven, terwijl elk platform zijn volledig native interface, prestaties en toegang tot de eigen SDK behoudt. In 2026 is KMP geen experiment meer: het is stabiel, officieel ondersteund door Google en draait in productie bij Netflix, McDonald's, Forbes en Cash App. Dit diepgaande artikel laat zien hoe het delen van code echt werkt, hoe een gedeelde module wordt geconfigureerd en welke afwegingen de moeite waard zijn vóór een KMP-sollicitatiegesprek.
Kotlin Multiplatform deelt gecompileerde logica, geen runtime. Gemeenschappelijke Kotlin-code wordt voor Android gecompileerd naar JVM-bytecode en voor iOS (via LLVM) naar een native binary. Er is geen JavaScript-bridge, geen ingebedde webview en geen reflectielaag, waardoor gedeelde code op beide platforms op native snelheid draait.
Hoe Kotlin Multiplatform code deelt zonder native prestaties op te geven
Het kernmechanisme is een hiërarchie van source sets. commonMain bevat platformonafhankelijke Kotlin: networking, serialisatie, bedrijfsregels en view-modellen. Platform-source-sets zoals androidMain en iosMain bevatten code die platform-API's aanspreekt. De compiler produceert een JVM-artefact uit commonMain plus androidMain voor Android en een native framework uit commonMain plus iosMain voor iOS.
Kotlin Multiplatform kiest er in 2026 voor om te delen wat echt baat heeft bij één bron van waarheid: modellen, API-clients, caching en validatie. De interface kan met Compose Multiplatform gedeeld worden of scherm voor scherm volledig native met SwiftUI blijven. Dit model ("deel de logica, kies de interface") is de reden waarom teams KMP invoeren zonder bestaande apps te herschrijven.
Kotlin Multiplatform is een Kotlin-technologie die één gedeelde module naar meerdere targets compileert (Android op de JVM, iOS als native, plus desktop en web), zodat de bedrijfslogica één keer wordt geschreven terwijl elk platform zijn native interface en volledige SDK-toegang behoudt. Google ondersteunt het nu als eersteklas optie, en Jetpack-bibliotheken zoals Room, DataStore, ViewModel en Paging bieden KMP-artefacten.
Beslissen wat in de gedeelde module thuishoort, is de belangrijkste architectuurkeuze in een KMP-project. De onderstaande tabel laat zien hoe de meeste productieteams de grens in 2026 trekken: alles onder de presentatielaag is een sterke kandidaat om te delen, terwijl alles wat platformspecifieke UX-conventies raakt meestal native blijft.
| Laag | Delen in commonMain? | Reden | |-------|---------------------|--------| | Datamodellen en DTO's | Ja | Identiek API-contract op beide platforms | | Networking en caching | Ja | Eén HTTP-stack, één foutoppervlak | | Bedrijfsregels en validatie | Ja | Het domein verschilt niet per besturingssysteem | | View-modellen en state | Meestal | Compose en SwiftUI gebruiken beide de gedeelde state | | Interface-rendering | Optioneel | Gedeeld met Compose of native per scherm | | Platform-API's (camera, biometrie) | Nee | In plaats daarvan via expect/actual beschikbaar maken |
De opbrengst schaalt mee met hoeveel van de onderste helft van die tabel een app beslaat. Datazware producten met lichte interfaces delen het meest; sterk op maat gemaakte, animatiegedreven apps delen het minst. Geen van beide uitersten is verkeerd, en daarom past KMP bij stapsgewijze invoering in plaats van een alles-of-niets-herschrijving.
De gedeelde module configureren in Gradle
Een KMP-module declareert zijn targets en source sets in Kotlin DSL. De volgende build.gradle.kts stelt Android en de drie iOS-architecturen in (fysiek toestel, Intel-simulator, Apple Silicon-simulator) en stelt de iOS-output beschikbaar als framework met de naam Shared.
plugins {
kotlin("multiplatform")
kotlin("plugin.serialization")
id("com.android.library")
}
kotlin {
// Android target compiles commonMain + androidMain to JVM bytecode
androidTarget()
// Three iOS targets: device, Intel sim, Apple Silicon sim
listOf(iosArm64(), iosX64(), iosSimulatorArm64()).forEach { target ->
target.binaries.framework {
baseName = "Shared" // becomes "import Shared" in Swift
isStatic = true
}
}
sourceSets {
commonMain.dependencies {
implementation("io.ktor:ktor-client-core:3.3.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
}
androidMain.dependencies {
implementation("io.ktor:ktor-client-okhttp:3.3.0")
}
iosMain.dependencies {
implementation("io.ktor:ktor-client-darwin:3.3.0")
}
}
}Elk target haalt een platformspecifieke Ktor-engine binnen (okhttp op Android, darwin op iOS), terwijl de gedeelde code alleen afhangt van de gemeenschappelijke ktor-client-core-API. De volledige set opties staat in de Kotlin Multiplatform-documentatie.
Platformspecifieke code schrijven met expect en actual
Sommige logica kan niet in commonMain staan omdat ze een platform-API nodig heeft. Het expect/actual-mechanisme declareert de vorm in gemeenschappelijke code en levert per target een concrete implementatie, opgelost tijdens het compileren zonder runtimekosten.
// commonMain declares what it needs but cannot implement here
expect class Platform() {
val name: String
val osVersion: String
}
// Shared logic can now depend on Platform freely
fun analyticsUserAgent(): String {
val platform = Platform()
return "SharpSkill/" + platform.name + " " + platform.osVersion
}De Android-implementatie leest uit de klasse Build en de iOS-implementatie uit UIDevice. Beide voldoen aan hetzelfde contract, dus commonMain weet nooit met welke van de twee het praat.
import android.os.Build
// actual supplies the Android-specific implementation
actual class Platform actual constructor() {
actual val name: String = "Android"
actual val osVersion: String = Build.VERSION.RELEASE
}import platform.UIKit.UIDevice
// actual uses UIKit directly, no Objective-C bridging code required
actual class Platform actual constructor() {
actual val name: String = "iOS"
actual val osVersion: String = UIDevice.currentDevice.systemVersion
}Kotlin/Native levert getypeerde bindings voor de volledige iOS-SDK, waardoor UIDevice, NSUserDefaults en Foundation-typen vanuit Kotlin aanroepbaar zijn zonder handgeschreven glue-code.
Networking- en serialisatielogica delen
Networking is de code met de hoogste waarde om te delen, omdat API-contracten op beide platforms identiek zijn. Eén Ktor-client plus kotlinx.serialization vervangt twee parallelle implementaties. De suspend-functies steunen op Kotlin-coroutines, die aan beide kanten netjes op async/await worden afgebeeld.
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.request.get
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.Serializable
// One serializable model, zero duplication across platforms
@Serializable
data class Question(val id: String, val prompt: String, val difficulty: String)
// One HttpClient, one JSON parser, one repository for both apps
class InterviewRepository {
private val client = HttpClient {
install(ContentNegotiation) { json() }
}
suspend fun fetchQuestions(tech: String): List<Question> =
client.get("https://api.sharpskill.dev/questions/" + tech).body()
}Dit repository, de bijbehorende retry-logica en de datamodellen worden één keer geschreven. Een bug die in de parser wordt opgelost, is tegelijk op Android en iOS opgelost, en juist daar verdient KMP de opzetkosten terug. Hetzelfde patroon strekt zich uit tot een gedeelde database met SQLDelight, gedeelde key-value-opslag en gedeelde dependency injection met Koin, zodat de hele datalaag in commonMain kan liggen achter interfaces waarvan de UI afhankelijk is.
Een veelgemaakte fout is te vroeg te veel willen delen. De pragmatische route is te beginnen met één repository, de build- en CI-pipeline op beide platforms te bewijzen en vervolgens functies één voor één te verplaatsen naarmate het vertrouwen groeit.
Klaar om je Android gesprekken te halen?
Oefen met onze interactieve simulatoren, flashcards en technische tests.
Gedeelde interface bouwen met Compose Multiplatform
Compose Multiplatform bereikte in mei 2025 de stabiele status op iOS (versie 1.8.0) en kwam medio 2026 uit op 1.11.0, met native tekstinvoer en standaard ingeschakelde concurrent rendering. Een @Composable in commonMain rendert op beide platforms, en gedeelde view-modellen passen van nature bij een MVVM- of MVI-architectuur.
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.layout.padding
// This list renders identically on Android and iOS
@Composable
fun QuestionList(questions: List<Question>) {
LazyColumn {
items(questions) { question ->
Card(modifier = Modifier.padding(8.dp)) {
Text(text = question.prompt, modifier = Modifier.padding(16.dp))
}
}
}
}De interface delen is optioneel en gebeurt per scherm. Een team kan de volledige interface delen met Compose Multiplatform, voor de hele app bij SwiftUI blijven en alleen de logica delen, of beide combineren. Schermen die afhangen van platformspecifieke UX, zoals widgets of App Clips, blijven meestal native.
Gedeelde code gebruiken vanuit Swift op iOS
De gedeelde module wordt gecompileerd tot een framework dat Swift importeert als elke andere bibliotheek. Het wrijvingspunt was van oudsher dat Kotlin-suspend-functies en Flow als onhandige callback-API's naar boven kwamen. SKIE van Touchlab lost dit op door idiomatisch Swift te genereren: suspend wordt native async/await en Flow wordt AsyncSequence.
import SwiftUI
import Shared // the KMP framework, baseName = "Shared"
struct ContentView: View {
@State private var questions: [Question] = []
private let repository = InterviewRepository()
var body: some View {
List(questions, id: \.id) { question in
Text(question.prompt)
}
.task {
// With SKIE, the Kotlin suspend fun is a Swift async fun
questions = (try? await repository.fetchQuestions(tech: "android")) ?? []
}
}
}Het iOS-team schrijft gewone Swift en SwiftUI. Het gebruikt gedeelde modellen en repositories precies zoals het een native Swift-package zou gebruiken, waardoor KMP onzichtbaar blijft voor het grootste deel van de iOS-codebase.
Twee ruwe kantjes zijn in een sollicitatiegesprek nog het vermelden waard. Ten eerste de buildtijden: Kotlin/Native-compilatie is trager dan een pure Swift-build, al verzachten de Gradle-buildcache en de distributie van een vooraf gebouwd XCFramework de dagelijkse kosten. Ten tweede verbetert het debuggen over de grens tussen Swift en Kotlin, maar het is nog niet zo naadloos als in één taal blijven, wat nog een reden is waarom teams het gedeelde oppervlak bewust beperkt houden in plaats van het te laten uitdijen.
Kotlin Multiplatform-sollicitatievragen om te verwachten
Omdat KMP-sollicitatievragen steeds vaker opduiken in Android- en mobiele functies, is het de moeite waard er een paar terugkerende te oefenen naast een bredere Android-sollicitatievoorbereiding:
- Waarin verschilt expect/actual van een interface? expect/actual wordt tijdens het compileren per target opgelost, zonder runtime-dispatch, en kan een klasse, functie, property of typealias onderbouwen. Een interface wordt dynamisch tijdens runtime opgelost. Gebruik expect/actual voor platform-API's en interfaces voor polymorfisme binnen gedeelde code.
- Hoe gaat Kotlin/Native om met geheugen en threading? Sinds Kotlin 1.7.20 heeft de nieuwe memory manager het oude object-freezing-model afgeschaft, waardoor gedeelde muteerbare state en coroutines over threads heen grotendeels werken zoals op de JVM.
- Kan een bestaande app KMP stapsgewijs invoeren? Ja. De gedeelde module wordt geleverd als AAR voor Android en als XCFramework voor iOS, zodat één repository of functie per keer kan migreren zonder herschrijving.
- Wanneer moet de interface native blijven in plaats van Compose Multiplatform te gebruiken? Wanneer een scherm leunt op platformspecifieke UX of wanneer het iOS-team de presentatielaag bezit en die liever zelf beheert.
Als richtpunt gelden Kotlin 2.3 met de K2-compiler, Ktor 3.x voor networking, SQLDelight voor een gedeelde database en Koin voor dependency injection. Voeg voor iOS-interoperabiliteit SKIE vroeg toe; het achteraf inbouwen nadat de Swift-laag is geschreven, is veel pijnlijker.
Conclusie
Kotlin Multiplatform is in 2026 een pragmatische manier om dubbele logica tussen Android en iOS te verminderen zonder native interface of prestaties op te geven:
- Deel de logica met één bron van waarheid (modellen, networking, validatie) en kies per scherm voor native of Compose-interface.
- Gebruik
expect/actualvoor platform-API's; het wordt tijdens het compileren opgelost zonder runtime-overhead. - Schrijf networking één keer met Ktor en
kotlinx.serialization, zodat correcties op beide platforms tegelijk aankomen. - Voer stapsgewijs in via een AAR en XCFramework in plaats van je vast te leggen op een herschrijving.
- Voeg SKIE vanaf het begin toe, zodat Swift
suspendenFlowgebruikt als nativeasync/awaitenAsyncSequence.
Begin met oefenen!
Test je kennis met onze gespreksimulatoren en technische tests.
Tags
Delen
Gerelateerde artikelen

Kotlin 2.3 voor Android: Naamgebaseerde Destructurering, KMP en Sollicitatievragen 2026
Kotlin 2.3 sollicitatievragen over naamgebaseerde destructurering, Kotlin Multiplatform, contextparameters, coroutines en Flow. Voorbereiding op Android-ontwikkelaarsgesprekken in 2026 met praktische codevoorbeelden.

Kotlin Flow vs StateFlow vs SharedFlow: Android-interviewvragen in 2026
De Kotlin Flow vs StateFlow vs SharedFlow-vragen die Android-interviewers in 2026 stellen, met heldere antwoorden, een vergelijkingstabel en productieklare code.

Android 16 in 2026: Nieuwe API's, Desktopmodus en Sollicitatievragen
Android 16 brengt ingrijpende veranderingen voor ontwikkelaars: verplicht edge-to-edge-rendering, desktopmodus, ProgressStyle-notificaties en predictive back-navigatie. Een technische deep-dive met codevoorbeelden en sollicitatievragen.