Configureer het eerste KMP-project: Android, iOS en desktop met Kotlin Multiplatform
Het helemaal opnieuw opzetten van een Kotlin Multiplatform-project is een van de meest technische stappen van het hele KMP-traject.
In tegenstelling tot Flutter waar flutter create produceert alles wat je nodig hebt, of React Native waar
Expo gaat om met complexiteit, KMP vereist een goed begrip van Gradle, de bronsetstructuur,
en het verwachten/actual-mechanisme voordat je de eerste regel gedeelde logica kunt schrijven.
Deze handleiding neemt u mee van het maken van uw blanco project naar een werkende applicatie met code gedeeld tussen Android en iOS, waarbij elke configuratiestap met gedetailleerde uitleg wordt weergegeven. Aan het einde heb je een project met de juiste structuur, de Gradle-versiecatalogus geconfigureerd en de eerste verwachten/werkelijke functies werken.
Vereisten
- Android Studio Hedgehog (2023.1.1) of hoger met KMP-plug-in geïnstalleerd
- Xcode 15+ (om de iOS-app te bouwen - alleen op macOS)
- JDK 17+ geconfigureerd als JAVA_HOME
- Basiskennis van Kotlin en Gradle
Stap 1: Maak het project aan met de KMP-wizard
De snelste manier om aan de slag te gaan is door gebruik te maken van de Kotlin Multiplatform-wizard beschikbaar
op het adres kmp.jetbrains.com of rechtstreeks vanuit Android Studio via
Bestand → Nieuw → Nieuw project → Kotlin Multiplatform-app.
# Alternativa da riga di comando con il KMP Wizard CLI (2026)
# Installa il plugin KMP di IntelliJ/Android Studio dal marketplace
# oppure usa il sito web:
# 1. Vai su https://kmp.jetbrains.com
# 2. Configura: nome progetto, package, target (Android, iOS, Desktop)
# 3. Scarica il progetto e aprilo in Android Studio
# Struttura generata dal wizard:
my-kmp-app/
├── composeApp/ # (se scegli Compose Multiplatform)
│ └── build.gradle.kts
├── shared/ # Il modulo condiviso principale
│ ├── src/
│ │ ├── commonMain/
│ │ ├── androidMain/
│ │ └── iosMain/
│ └── build.gradle.kts
├── androidApp/ # App Android standalone
│ └── build.gradle.kts
├── iosApp/ # App iOS standalone (Swift)
│ └── iosApp.xcodeproj
├── gradle/
│ └── libs.versions.toml # Version catalog
├── build.gradle.kts # Root build script
└── settings.gradle.kts
Stap 2: De versiecatalogus (libs.versions.toml)
Il Gradle-versiecatalogus centraliseert het versiebeheer van alles projectafhankelijkheden in één TOML-bestand. Het is de beste praktijk die wordt aanbevolen door JetBrains voor KMP-projecten:
# gradle/libs.versions.toml
[versions]
kotlin = "2.0.20"
kotlinx-coroutines = "1.9.0"
kotlinx-serialization = "1.7.3"
ktor = "2.3.12"
sqldelight = "2.0.2"
koin = "3.5.6"
agp = "8.5.2"
compose-multiplatform = "1.7.0"
kotlinx-datetime = "0.6.1"
[libraries]
# Kotlin
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
# Coroutines
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
# Serialization
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
# Ktor
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-serialization-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
# SQLDelight
sqldelight-runtime = { module = "app.cash.sqldelight:runtime", version.ref = "sqldelight" }
sqldelight-android-driver = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }
sqldelight-native-driver = { module = "app.cash.sqldelight:native-driver", version.ref = "sqldelight" }
sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" }
# Koin DI
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" }
# DateTime
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
compose-multiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
Stap 3: Het bouwscript voor de gedeelde module
Il build.gradle.kts van de gedeelde module is het belangrijkste configuratiebestand
van het project. Definieert de builddoelen, bronsets en afhankelijkheden van elk:
// shared/build.gradle.kts
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.sqldelight)
}
kotlin {
// Target Android
androidTarget {
compilations.all {
kotlinOptions {
jvmTarget = "17"
}
}
}
// Target iOS (arm64 per device, x64 per simulator Intel, simulatorArm64 per M1/M2)
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "Shared"
isStatic = true
}
}
// Target Desktop JVM (opzionale)
jvm("desktop")
sourceSets {
// Codice comune a tutte le piattaforme
commonMain.dependencies {
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.serialization.json)
implementation(libs.sqldelight.runtime)
implementation(libs.sqldelight.coroutines)
implementation(libs.koin.core)
implementation(libs.kotlinx.datetime)
}
// Test comuni
commonTest.dependencies {
implementation(libs.kotlin.test)
}
// Android-specific
androidMain.dependencies {
implementation(libs.ktor.client.okhttp)
implementation(libs.sqldelight.android.driver)
implementation(libs.kotlinx.coroutines.android)
}
// iOS-specific
val iosMain by getting {
// Su iOS non puoi usare "iosMain" direttamente se hai piu target iOS
// Usa una convenzione condivisa per i tre target iOS
}
val iosX64Main by getting
val iosArm64Main by getting
val iosSimulatorArm64Main by getting
// Source set condiviso per tutti i target iOS
create("iosMain") {
dependsOn(commonMain.get())
iosX64Main.dependsOn(this)
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
dependencies {
implementation(libs.ktor.client.darwin)
implementation(libs.sqldelight.native.driver)
}
}
}
}
android {
namespace = "com.example.mykmpapp.shared"
compileSdk = 35
defaultConfig {
minSdk = 26
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}
sqldelight {
databases {
create("AppDatabase") {
packageName.set("com.example.mykmpapp.db")
}
}
}
Stap 4: Bronsets begrijpen
I bron ingesteld zij vormen de fundamentele structuur van KMP. Elke bronset is er één
mappen met Kotlin-code met zijn afhankelijkheden, en bronsets kunnen van anderen afhankelijk zijn
via de hiërarchie dependsOn:
// Gerarchia dei source set tipica
commonMain
├── androidMain (usa librerie Android: OkHttp, Android SQLite)
├── iosMain (usa librerie Darwin: URLSession, iOS SQLite)
│ ├── iosX64Main
│ ├── iosArm64Main
│ └── iosSimulatorArm64Main
└── desktopMain (usa librerie JVM: OkHttp, SQLite JDBC)
// Tutti i source set "eredita" le dipendenze di commonMain
// androidMain puo usare tutto di commonMain + dipendenze Android-specific
In het projectbestandssysteem weerspiegelt de directorystructuur de bronsets:
shared/src/
├── commonMain/
│ └── kotlin/
│ └── com/example/mykmpapp/
│ ├── data/
│ ├── domain/
│ └── Platform.kt # expect declaration
├── androidMain/
│ └── kotlin/
│ └── com/example/mykmpapp/
│ └── Platform.android.kt # actual per Android
└── iosMain/
└── kotlin/
└── com/example/mykmpapp/
└── Platform.ios.kt # actual per iOS
Stap 5: De eerste verwachte/werkelijke functies
Het mechanisme verwachten/werkelijk en hoe KMP omgaat met platformspecifieke verschillen.
expect declareer de API in gemeenschappelijke code, actual implementeert het voor iedereen
platform. Laten we beginnen met een klassiek voorbeeld: informatie verkrijgen over het huidige platform:
// commonMain/kotlin/com/example/mykmpapp/Platform.kt
expect class PlatformInfo() {
val name: String
val version: String
val isDebug: Boolean
}
// Funzione che usa l'implementazione platform-specific
fun greeting(): String = "Running on ${PlatformInfo().name} ${PlatformInfo().version}"
// androidMain/kotlin/com/example/mykmpapp/Platform.android.kt
import android.os.Build
actual class PlatformInfo {
actual val name: String = "Android ${Build.VERSION.RELEASE}"
actual val version: String = Build.VERSION.SDK_INT.toString()
actual val isDebug: Boolean = BuildConfig.DEBUG
}
// iosMain/kotlin/com/example/mykmpapp/Platform.ios.kt
import platform.UIKit.UIDevice
actual class PlatformInfo {
actual val name: String = UIDevice.currentDevice.systemName()
actual val version: String = UIDevice.currentDevice.systemVersion
actual val isDebug: Boolean = Platform.isDebugBinary
}
Een tweede, meer praktisch voorbeeld: het genereren van UUID (die verschillende API’s per platform gebruikt):
// commonMain: dichiarazione expect
expect fun generateUUID(): String
// androidMain: implementazione con java.util.UUID
actual fun generateUUID(): String = java.util.UUID.randomUUID().toString()
// iosMain: implementazione con NSUUID di iOS
import platform.Foundation.NSUUID
actual fun generateUUID(): String = NSUUID().UUIDString()
Stap 6: Configureer de Android-app
De Android-app is een standaard Gradle-module die afhankelijk is van de gedeelde module. De configuratie en simpel:
// androidApp/build.gradle.kts
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
}
android {
namespace = "com.example.mykmpapp.android"
compileSdk = 35
defaultConfig {
applicationId = "com.example.mykmpapp"
minSdk = 26
targetSdk = 35
versionCode = 1
versionName = "1.0"
}
buildFeatures {
compose = true
}
}
dependencies {
// Dipende dal modulo shared
implementation(projects.shared)
// UI Android (Compose o View-based)
implementation(platform("androidx.compose:compose-bom:2026.01.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material3:material3")
implementation("androidx.activity:activity-compose:1.9.3")
}
Stap 7: Configureer de iOS-app
iOS-integratie vereist dat u Xcode zo configureert dat het het gecompileerde KMP-framework bevat.
De gedeelde module is gecompileerd in een iOS-framework (Shared.xcframework) dat
is gekoppeld aan de Xcode-app:
# Script di build iOS da aggiungere come Xcode Build Phase:
# "Run Script" - da aggiungere in Build Phases di Xcode
cd "$SRCROOT/.."
# Compila il framework KMP per il target iOS corrente
if [ "$PLATFORM_NAME" = "iphonesimulator" ]; then
if [ "$ARCHS" = "arm64" ]; then
TARGET="iosSimulatorArm64"
else
TARGET="iosX64"
fi
else
TARGET="iosArm64"
fi
./gradlew "shared:link${TARGET}DebugFrameworkIos${TARGET^}" \
-Pkotlin.native.useEmbeddableCompilerJar=true
# Il framework viene copiato automaticamente da Gradle
In het Swift-bestand van de iOS-app wordt het framework geïmporteerd als een gewoon Swift-framework:
// iosApp/ContentView.swift
import SwiftUI
import Shared // Il framework KMP compilato
struct ContentView: View {
@State private var greeting = ""
var body: some View {
VStack {
Text(greeting)
.padding()
Button("Refresh") {
greeting = Greeting().greeting()
}
}
.onAppear {
// Chiama il codice Kotlin dal framework condiviso
greeting = PlatformInfoKt.greeting()
}
}
}
Stap 8: Eerste test van de gedeelde module
Controleer of alles werkt door een unittest in de gedeelde module te schrijven en uit te voeren.
Testen in algemene toepassingen kotlin.test die werkt op alle platforms:
// commonTest/kotlin/com/example/mykmpapp/PlatformTest.kt
import kotlin.test.Test
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
class PlatformTest {
@Test
fun testPlatformInfoNotNull() {
val info = PlatformInfo()
assertNotNull(info.name)
assertNotNull(info.version)
}
@Test
fun testGreetingContainsPlatform() {
val greet = greeting()
assertTrue(greet.contains("Running on"), "Greeting dovrebbe contenere 'Running on'")
}
@Test
fun testUUIDFormat() {
val uuid = generateUUID()
// UUID formato: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
assertTrue(uuid.length == 36, "UUID dovrebbe avere 36 caratteri")
assertTrue(uuid.count { it == '-' } == 4, "UUID dovrebbe avere 4 trattini")
}
}
# Esegui i test sul target JVM (piu veloce, per CI)
./gradlew shared:jvmTest
# Esegui i test su Android (emulatore o device)
./gradlew shared:connectedAndroidTest
# Esegui i test iOS (solo su macOS)
./gradlew shared:iosSimulatorArm64Test
Veelvoorkomende problemen en oplossingen
- Foutmelding 'Kotlin/Native toolchain niet gevonden': Zorg ervoor dat je dat hebt gedaan de JDK 17+ geconfigureerd als JAVA_HOME en dat Gradle de toolchain heeft gedownload Kotlin/Native (de eerste build is om deze reden langzamer).
-
Fout 'werkelijke aangifte niet gevonden': Je gebruikt
expectzonder een overeenkomstige te verstrekkenactualvoor alle geconfigureerde doelen. Controleer of de daadwerkelijke klasse/functie bestaat in alle bronsets die zijn geconfigureerd inkotlin { }. - Verouderd iOS-framework: Na wijzigingen in de Kotlin-code, maak de Xcode-build schoon (Cmd+Shift+K) en compileer opnieuw. Het raamwerk moet dat zijn opnieuw gecompileerd met Gradle voordat Xcode het kan zien.
- Compatibiliteit met bibliotheek: Niet alle Java-bibliotheken werken erop iOS. Gebruik altijd bibliotheken met de tag "KMP" of "Kotlin Multiplatform" in de README.
Structuur van het eindproject
Aan het einde van deze configuratie heeft uw project deze operationele structuur:
my-kmp-app/
├── gradle/
│ └── libs.versions.toml # Version catalog centralizzato
├── shared/ # Modulo KMP condiviso
│ ├── src/
│ │ ├── commonMain/kotlin/ # Logica condivisa
│ │ ├── commonTest/kotlin/ # Test condivisi
│ │ ├── androidMain/kotlin/ # Android-specific
│ │ └── iosMain/kotlin/ # iOS-specific
│ └── build.gradle.kts
├── androidApp/ # App Android
│ ├── src/main/...
│ └── build.gradle.kts
├── iosApp/ # App iOS (Xcode project)
│ ├── iosApp/
│ │ ├── ContentView.swift
│ │ └── iOSApp.swift
│ └── iosApp.xcodeproj
├── build.gradle.kts # Root (plugin declarations)
└── settings.gradle.kts # Moduli inclusi
Conclusies en volgende stappen
Je hebt een compleet KMP-project opgezet met Android, iOS en de gedeelde module. De bocht De leercurve van Gradle en bronsets is in het begin steil, maar de structuur die je hebt gemaakt en robuust en schaalbaar voor bedrijfsprojecten.
Het volgende artikel gaat dieper in op degedeelde modulearchitectuur: hoe je verwacht/actueel gebruikt voor complexere patronen, hoe je Koin instelt voor de afhankelijkheid injectie op meerdere platforms, en hoe u de code zo kunt structureren dat deze gemakkelijk testbaar is geïsoleerd.
Serie: Kotlin Multiplatform – Eén Codebase, alle platforms
- Artikel 1: KMP in 2026 — Architectuur, vestiging en ecosysteem
- Artikel 2 (dit): Configureer het eerste KMP-project: Android, iOS en desktop
- Artikel 3: Gedeelde modulearchitectuur – verwacht/actueel, interfaces en DI
- Artikel 4: Multiplatformnetwerken met Ktor Client
- Artikel 5: Volharding op meerdere platforms met SQLDelight
- Artikel 6: Stel multiplatform samen – gedeelde gebruikersinterface op Android en iOS
- Artikel 7: Staatsbeheer KMP — ViewModel en Kotlin Flows
- Artikel 8: KMP-testen – Unittest, Integratietest en UI-test
- Artikel 9: Swift Export – Idiomatische interoperabiliteit met iOS
- Artikel 10: CI/CD voor KMP-projecten — GitHub Actions en Fastlane
- Artikel 11: Case Study — Fintech-app met KMP in productie







