404 Not Found

404 Not Found


nginx

Kotlin Multiplatform Introduction

KMP is Kotlin's ultimate vision — Charlie's OrderValidator is written once and reused across JVM backend, iOS app, JS frontend, and Wasm browser. The expect/actual mechanism keeps platform differences only at the boundaries.

1. What You'll Learn


2. A Real Architect's Story

(1) Pain Point: Triplicating Business Logic Across Three Platforms

Charlie's company has a JVM backend, an iOS app, and a JS frontend. The same OrderValidator logic was implemented three times in Java, Swift, and TypeScript. Fixing one bug required changes in three places — this caused two inconsistent-behavior incidents in one month.

(2) KMP's Shared Code Solution

KOTLIN
// commonMain: ONE implementation for all platforms
class OrderValidator {
    fun validate(order: Order): List<String> {
        val errors = mutableListOf<String>()
        if (!order.id.startsWith("ORD-")) errors.add("Invalid ID")
        if (order.total < 0) errors.add("Negative total")
        return errors
    }
}

One codebase, three platforms. Fix a bug once, and behavior is naturally consistent everywhere.


3. KMP Architecture

(1) Project Structure

TEXT
shared/
├── src/
│   ├── commonMain/kotlin/       # Shared code (all platforms)
│   │   └── com/order/
│   │       ├── Order.kt
│   │       ├── OrderValidator.kt
│   │       └── Platform.kt      # expect declarations
│   ├── commonTest/kotlin/       # Shared tests
│   ├── jvmMain/kotlin/          # JVM-specific code
│   │   └── com/order/
│   │       └── Platform.kt      # actual for JVM
│   ├── iosMain/kotlin/          # iOS-specific code
│   │   └── com/order/
│   │       └── Platform.kt      # actual for iOS
│   └── jsMain/kotlin/           # JS-specific code
│       └── com/order/
│           └── Platform.kt      # actual for JS
└── build.gradle.kts

(2) expect / actual Mechanism

KOTLIN
// commonMain: declare what you need (expect)
expect fun getPlatformName(): String
expect class DateFormatter() {
    fun format(timestamp: Long): String
}

// jvmMain: provide JVM implementation (actual)
actual fun getPlatformName(): String = "JVM"
actual class DateFormatter actual constructor() {
    actual fun format(timestamp: Long): String =
        java.text.SimpleDateFormat("yyyy-MM-dd").format(timestamp)
}

// iosMain: provide iOS implementation (actual)
actual fun getPlatformName(): String = "iOS"
actual class DateFormatter actual constructor() {
    actual fun format(timestamp: Long): String {
        // Use NSDateFormatter
        return NSDateFormatter().apply {
            dateFormat = "yyyy-MM-dd"
        }.stringFromDate(NSDate(timestamp / 1000.0))
    }
}

// jsMain: provide JS implementation (actual)
actual fun getPlatformName(): String = "JS"
actual class DateFormatter actual constructor() {
    actual fun format(timestamp: Long): String {
        // Use JavaScript Date
        return js("new Date(timestamp).toISOString().split('T')[0]")
    }
}

(3) KMP Multiplatform Architecture

100%
flowchart TD
    A[commonMain<br/>Shared Business Logic] --> B[jvmMain<br/>JVM Actual]
    A --> C[iosMain<br/>iOS Actual]
    A --> D[jsMain<br/>JS Actual]
    A --> E[wasmMain<br/>Wasm Actual]
    B --> B1[Spring Boot<br/>Backend]
    C --> C1[iOS App<br/>Swift Interop]
    D --> D1[Node.js / Browser]
    E --> E1[Web Assembly Runtime]

4. Gradle Multiplatform Configuration

(1) build.gradle.kts

KOTLIN
plugins {
    kotlin("multiplatform") version "1.9.22"
}

group = "com.order"
version = "1.0.0"

repositories {
    mavenCentral()
}

kotlin {
    // Declare target platforms
    jvm {
        compilations.all {
            kotlinOptions.jvmTarget = "17"
        }
        testRuns["test"].executionTask.configure {
            useJUnitPlatform()
        }
    }
    iosX64()
    iosArm64()
    iosSimulatorArm64()
    js(IR) {
        browser()
        nodejs()
    }

    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
                implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
            }
        }
        val commonTest by getting {
            dependencies {
                implementation(kotlin("test"))
            }
        }
        val jvmMain by getting
        val jvmTest by getting
        val iosMain by getting
        val jsMain by getting
    }
}

5. Shared Module Strategy

(1) What to Share and What Not to Share

Layer Sharing Strategy Reason
Data models ✅ Fully shared Pure data, no platform dependency
Business logic ✅ Fully shared Core rules must be consistent
Networking ✅ Shared interface + expect HTTP Interface unified, implementations differ
Serialization ✅ Shared (kotlinx.serialization) Multi-format native support
UI ❌ Platform-specific UI frameworks differ significantly
Database ⚠️ Shared SQL / expect SQL sharable, drivers differ
Logging ⚠️ expect/actual Platform logging APIs differ

(2) Shared Strategy Patterns

KOTLIN
// Pattern 1: Pure shared code (no expect/actual needed)
data class Order(val id: String, val total: Double, val status: String)

class OrderValidator {
    fun validate(order: Order): List<String> = buildList {
        if (!order.id.startsWith("ORD-")) add("Invalid ID format")
        if (order.total < 0) add("Negative total")
        if (order.status !in validStatuses) add("Invalid status")
    }

    companion object {
        private val validStatuses = setOf("PENDING", "CONFIRMED", "SHIPPED", "CANCELLED")
    }
}

// Pattern 2: Interface + expect factory
interface HttpClient {
    suspend fun get(url: String): String
    suspend fun post(url: String, body: String): String
}

expect fun createHttpClient(): HttpClient

// Pattern 3: expect function for platform-specific behavior
expect fun logDebug(tag: String, message: String)

6. Interoperability

(1) Platform Interoperability Methods

Platform Pair Interoperability Notes
JVM ↔ Java Seamless bidirectional Kotlin calls Java directly; Java can call Kotlin
iOS ↔ Swift Bidirectional Kotlin compiles to Obj-C framework; Swift calls seamlessly
JS ↔ JavaScript Bidirectional js() function calls JS; @JsExport exports Kotlin
Wasm ↔ JS One-way (JS calls Kotlin) Wasm module exports functions for JS calls

(2) JVM Interoperability Example

KOTLIN
// Kotlin calling Java
val order = JavaOrderService()  // Java class
order.processOrder("ORD-001")   // Java method

// Java calling Kotlin (generated bytecode is standard)
// OrderKt.processOrder(order);  // Top-level function

(3) JS Interoperability Example

KOTLIN
// Kotlin calling JavaScript
fun fetchFromApi(url: String): dynamic {
    return js("fetch(url).then(r => r.json())")
}

// Export Kotlin to JavaScript
@JsExport
class OrderValidator {
    fun validate(id: String, total: Double): Boolean {
        return id.startsWith("ORD-") && total >= 0
    }
}

7. Complete Example: Cross-Platform OrderValidator

KOTLIN
// ============================================
// OrderProcessor - KMP Shared Module
// Feature: Shared OrderValidator with platform logging
// ============================================

// --- commonMain ---

data class Order(val id: String, val total: Double, var status: String, val customer: String)

// expect: platform-specific declaration
expect fun logInfo(tag: String, message: String)

class OrderValidator {
    fun validate(order: Order): ValidationResult {
        val errors = mutableListOf<String>()

        if (!order.id.startsWith("ORD-")) errors.add("Invalid ID format: ${order.id}")
        if (order.total < 0) errors.add("Negative total: ${order.total}")
        if (order.total > 1_000_000) errors.add("Total exceeds maximum: ${order.total}")
        if (order.status !in VALID_STATUSES) errors.add("Invalid status: ${order.status}")

        val result = if (errors.isEmpty()) ValidationResult.Valid else ValidationResult.Invalid(errors)

        logInfo("OrderValidator", "Validated ${order.id}: $result")
        return result
    }

    fun calculatePriority(order: Order): String = when {
        order.total > 10_000 -> "HIGH"
        order.total > 1_000 -> "MEDIUM"
        else -> "LOW"
    }

    companion object {
        private val VALID_STATUSES = setOf("PENDING", "CONFIRMED", "SHIPPED", "DELIVERED", "CANCELLED")
    }
}

sealed class ValidationResult {
    object Valid : ValidationResult()
    data class Invalid(val errors: List<String>) : ValidationResult()
}

// --- jvmMain ---
// actual fun logInfo(tag: String, message: String) {
//     println("[$tag] $message")  // Or use SLF4J
// }

// --- iosMain ---
// actual fun logInfo(tag: String, message: String) {
//     NSLog("$tag: $message")
// }

// --- jsMain ---
// actual fun logInfo(tag: String, message: String) {
//     console.log("[$tag] $message")
// }

// --- Demo (JVM target) ---
fun main() {
    // Simulate JVM actual
    // actual fun logInfo(tag: String, message: String) = println("[$tag] $message")

    val validator = OrderValidator()

    val orders = listOf(
        Order("ORD-001", 299.99, "PENDING", "Alice"),
        Order("BAD-002", -50.0, "INVALID", "Bob"),
        Order("ORD-003", 15_000.00, "CONFIRMED", "Charlie"),
        Order("ORD-004", 2_000_000.00, "PENDING", "Dave")
    )

    println("=== KMP OrderValidator Demo ===")
    orders.forEach { order ->
        val result = validator.validate(order)
        val priority = validator.calculatePriority(order)
        when (result) {
            is ValidationResult.Valid -> println("  ✅ ${order.id}: Valid (Priority: $priority)")
            is ValidationResult.Invalid -> println("  ❌ ${order.id}: ${result.errors}")
        }
    }
}

Output:

TEXT
=== KMP OrderValidator Demo ===
  ✅ ORD-001: Valid (Priority: LOW)
  ❌ BAD-002: [Invalid ID format: BAD-002, Negative total: -50.0, Invalid status: INVALID]
  ✅ ORD-003: Valid (Priority: HIGH)
  ❌ ORD-004: [Total exceeds maximum: 2000000.0]

❓ FAQ

Q What's the difference between KMP and Flutter?
A KMP shares business logic (native UI per platform); Flutter shares UI (Skia rendering). KMP is more flexible (preserving native UI experience); Flutter is more unified (single UI). They can complement each other.
Q Is KMP production-ready?
A JVM and Android targets are fully stable; iOS target is stable; JS and Wasm targets are rapidly maturing. Companies like Netflix, VMware, and Cash App already use it at scale in production.
Q Can expect/actual be used for classes?
A Yes. expect class declares a cross-platform interface; actual class provides platform implementations. Constructor and method signatures must match exactly.
Q How should shared and platform modules be organized?
A Put shared logic in commonMain; isolate platform differences with expect/actual at the boundary. Avoid platform-specific code in commonMain — use expect to abstract it away.
Q Is KMP friendly to iOS developers?
A Very friendly. KMP compiles to an Obj-C framework that Swift calls seamlessly. iOS developers only need to care about the Swift side — they don't need to know Kotlin.
Q What's the build speed like for KMP?
A Multi-target compilation increases build time (each target compiles independently). Gradle incremental compilation and Kotlin compiler caching help mitigate this. In CI/CD, parallel target builds are recommended.

📖 Summary


📝 Exercises

  1. Beginner (⭐): Create a KMP project, define Order in commonMain, and use it in JVM and JS targets. Hint: kotlin("multiplatform") Gradle plugin
  2. Intermediate (⭐⭐): Use expect/actual to make getPlatformName() return different values on JVM and JS. Hint: expect fun getPlatformName(): String + actual implementations
  3. Challenge (⭐⭐⭐): Implement a cross-platform HTTP client: define HttpClient interface in commonMain, implement with java.net.URL in jvmMain and with the fetch API in jsMain. Hint: expect fun createHttpClient(): HttpClient

← Previous | Next →

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏