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().UUIDString

Shared 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.

Leave a Comment

Your email address will not be published. Required fields are marked *