Kotlin Multiplatform Intro
Kotlin Multiplatform (KMP) lets you write shared business logic once in Kotlin and use it on Android, iOS, web, and desktop — while each platform keeps its own native UI. You stop duplicating logic and bugs across platforms without giving up native user experiences.
The Problem KMP Solves
Without KMP:
Android team: UserRepository.kt (Kotlin)
iOS team: UserRepository.swift (Swift)
Web team: userRepository.ts (TypeScript)
Same logic — written 3 times — bugs fixed 3 times separately.
With KMP:
Shared module: UserRepository.kt (Kotlin)
↓ ↓ ↓
Android iOS Web
(native UI) (native UI) (native UI)
One source of truth. Platform-specific UI stays native.
KMP Project Structure
MyKMPApp/
├── shared/ ← KMP shared module
│ └── src/
│ ├── commonMain/kotlin/ ← code for ALL platforms
│ │ ├── domain/
│ │ │ └── User.kt
│ │ └── repository/
│ │ └── UserRepository.kt
│ ├── androidMain/kotlin/ ← Android-specific implementations
│ ├── iosMain/kotlin/ ← iOS-specific implementations
│ └── jvmMain/kotlin/ ← JVM-specific implementations
│
├── androidApp/ ← Android app (uses shared module)
│ └── src/main/kotlin/
│ └── MainActivity.kt
│
└── iosApp/ ← iOS app (uses shared module via framework)
└── iosApp/
└── ContentView.swift
Writing Shared Code
// shared/src/commonMain/kotlin/domain/User.kt
data class User(
val id: Int,
val name: String,
val email: String
)
// shared/src/commonMain/kotlin/repository/UserRepository.kt
class UserRepository {
private val users = mutableListOf(
User(1, "Alice", "alice@mail.com"),
User(2, "Bob", "bob@mail.com"),
User(3, "Carol", "carol@mail.com")
)
fun getAllUsers(): List = users.toList()
fun getUserById(id: Int): User? = users.find { it.id == id }
fun addUser(user: User) {
require(users.none { it.id == user.id }) { "User ${user.id} already exists" }
users.add(user)
}
fun searchByName(query: String): List =
users.filter { it.name.contains(query, ignoreCase = true) }
} expect / actual: Platform-Specific Code
Use expect in common code to declare a function that each platform will implement differently:
// commonMain — declaration only
expect fun getPlatformName(): String
expect fun currentTimeMillis(): Long
expect fun generateId(): String
// androidMain — Android implementation
actual fun getPlatformName() = "Android ${android.os.Build.VERSION.SDK_INT}"
actual fun currentTimeMillis() = System.currentTimeMillis()
actual fun generateId() = java.util.UUID.randomUUID().toString()
// iosMain — iOS implementation
actual fun getPlatformName() = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
actual fun currentTimeMillis() = (NSDate().timeIntervalSince1970 * 1000).toLong()
actual fun generateId() = NSUUID().UUIDStringShared Business Logic Example
// commonMain — works on all platforms
class PasswordValidator {
fun validate(password: String): ValidationResult {
val errors = mutableListOf()
if (password.length < 8) errors.add("At least 8 characters required")
if (!password.any { it.isUpperCase() }) errors.add("At least one uppercase letter required")
if (!password.any { it.isDigit() }) errors.add("At least one digit required")
return if (errors.isEmpty()) ValidationResult.Valid
else ValidationResult.Invalid(errors)
}
}
sealed class ValidationResult {
object Valid : ValidationResult()
data class Invalid(val errors: List) : ValidationResult()
}
// This same class works on Android, iOS, and Web without any changes. Using Shared Module in Android
// androidApp/MainActivity.kt
class MainActivity : AppCompatActivity() {
private val repo = UserRepository() // from shared module
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val users = repo.getAllUsers()
users.forEach { user ->
println("${user.name}: ${user.email}")
}
val found = repo.searchByName("ali")
println("Found: $found")
}
}Kotlin Multiplatform vs Other Solutions
KMP Flutter React Native
──────────────────────────────────────────────────────────
UI Framework Native Flutter React Native
(each platform (Dart custom (JS bridged)
uses own UI) renderer)
Shared layer Business logic Everything Business logic
Language Kotlin Dart JavaScript/TS
Performance Native Good Good
Maturity Growing Mature Mature
Setting Up KMP (Gradle)
// shared/build.gradle.kts
plugins {
kotlin("multiplatform")
}
kotlin {
androidTarget()
iosX64()
iosArm64()
iosSimulatorArm64()
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
}
}
val androidMain by getting
val iosMain by getting
}
}Kotlin Multiplatform is officially stable and used in production by companies like Netflix, VMware, and Philips. It is the most pragmatic path to sharing code between Android and iOS while keeping full native control over the user interface on each platform.
