Skip to main content

Getting Started with Kotlin Multiplatform

Learn how to build cross-platform libraries with Kotlin Multiplatform (KMP).

What is Kotlin Multiplatform?​

Kotlin Multiplatform allows you to share code between multiple platforms while maintaining native performance. Write your business logic once and use it across:

  • Android (native)
  • iOS (native)
  • Web (JavaScript/Wasm)
  • Desktop (JVM, Windows, macOS, Linux)
  • Server (JVM, Node.js)

Prerequisites​

Before you begin, install:

  • JDK 17 or higher
  • Kotlin 1.9.0 or higher
  • Gradle 8.0 or higher
  • Android Studio or IntelliJ IDEA
  • Xcode (for iOS development on macOS)

Setting Up Your Environment​

1. Verify Installation​

java -version
kotlin -version
gradle --version

2. Create a New KMP Library​

Using the Kotlin Multiplatform wizard:

# Using IntelliJ IDEA or Android Studio
# File → New → Project → Kotlin Multiplatform Library

Or use the Gradle template:

mkdir my-kmp-library
cd my-kmp-library
gradle init --type kotlin-library

Project Structure​

A typical KMP library structure:

my-kmp-library/
├── src/
│ ├── commonMain/
│ │ └── kotlin/ # Shared code
│ ├── androidMain/
│ │ └── kotlin/ # Android-specific
│ ├── iosMain/
│ │ └── kotlin/ # iOS-specific
│ ├── jvmMain/
│ │ └── kotlin/ # JVM-specific
│ └── jsMain/
│ └── kotlin/ # JavaScript-specific
├── build.gradle.kts
├── gradle.properties
└── settings.gradle.kts

Basic Configuration​

build.gradle.kts​

plugins {
kotlin("multiplatform") version "1.9.20"
id("com.android.library")
id("maven-publish")
}

group = "dev.brewkits"
version = "1.0.0"

kotlin {
// Configure targets
android()
ios()
iosSimulatorArm64()

jvm()
js(IR) {
browser()
nodejs()
}

// Configure source sets
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
}
}

val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}

val androidMain by getting {
dependencies {
implementation("androidx.core:core-ktx:1.12.0")
}
}

val iosMain by getting
val iosSimulatorArm64Main by getting {
dependsOn(iosMain)
}
}
}

android {
namespace = "dev.brewkits.library"
compileSdk = 34

defaultConfig {
minSdk = 24
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}

Writing Shared Code​

Common Code (commonMain)​

// src/commonMain/kotlin/dev/brewkits/library/Greeting.kt
package dev.brewkits.library

class Greeting {
fun greet(): String {
return "Hello from ${getPlatform().name}!"
}
}

expect fun getPlatform(): Platform

interface Platform {
val name: String
}

Platform-Specific Implementations​

Android (androidMain):

// src/androidMain/kotlin/dev/brewkits/library/Platform.android.kt
package dev.brewkits.library

actual fun getPlatform(): Platform = AndroidPlatform()

class AndroidPlatform : Platform {
override val name: String = "Android ${android.os.Build.VERSION.SDK_INT}"
}

iOS (iosMain):

// src/iosMain/kotlin/dev/brewkits/library/Platform.ios.kt
package dev.brewkits.library

import platform.UIKit.UIDevice

actual fun getPlatform(): Platform = IOSPlatform()

class IOSPlatform : Platform {
override val name: String =
"${UIDevice.currentDevice.systemName()} ${UIDevice.currentDevice.systemVersion}"
}

Testing​

Common Tests​

// src/commonTest/kotlin/dev/brewkits/library/GreetingTest.kt
package dev.brewkits.library

import kotlin.test.Test
import kotlin.test.assertTrue

class GreetingTest {
@Test
fun testGreeting() {
val greeting = Greeting().greet()
assertTrue(greeting.contains("Hello"))
}
}

Run Tests​

# Run all tests
./gradlew allTests

# Run tests for specific platform
./gradlew jvmTest
./gradlew androidInstrumentedTest
./gradlew iosSimulatorArm64Test

Building​

# Build all targets
./gradlew build

# Build specific target
./gradlew jvmJar
./gradlew assembleDebug # Android
./gradlew linkDebugFrameworkIosArm64 # iOS

Best Practices​

1. Maximize Shared Code​

Put as much logic as possible in commonMain:

// ✅ Good - business logic in commonMain
class UserRepository {
fun validateEmail(email: String): Boolean {
return email.contains("@")
}
}

// Platform-specific only for platform APIs
expect class NetworkClient() {
fun makeRequest(url: String): String
}

2. Use expect/actual for Platform APIs​

// commonMain
expect class FileStorage {
fun save(key: String, value: String)
fun load(key: String): String?
}

// androidMain
actual class FileStorage {
private val prefs = /* SharedPreferences */

actual fun save(key: String, value: String) {
prefs.edit().putString(key, value).apply()
}
}

// iosMain
actual class FileStorage {
actual fun save(key: String, value: String) {
NSUserDefaults.standardUserDefaults.setObject(value, key)
}
}

3. Leverage Kotlin Coroutines​

class DataService {
suspend fun fetchData(): Result<Data> = withContext(Dispatchers.IO) {
try {
val response = api.fetch()
Result.success(response)
} catch (e: Exception) {
Result.failure(e)
}
}
}

Next Steps​

Resources​


Start building once, deploy everywhere with Kotlin Multiplatform!