Step 2 of 7
expect/actual mechanism, writing common APIs, platform-specific implementations
expect/actual — เขียน common interface แล้ว implement ต่างกันในแต่ละ platform
KMP splits code into two places:
commonMain) — runs everywhere. Pure Kotlin, no platform APIs.androidMain, iosMain) — uses Android or iOS APIs.When common code needs a platform feature, you use the expect/actual mechanism. You declare a contract in commonMain, then provide an implementation in each platform source set.
The cleanest pattern is to define an interface in commonMain and implement it per platform.
// commonMain
interface Platform {
val name: String
fun osVersion(): String
}
class Greeting(private val platform: Platform) {
fun greet(): String = "Running on ${platform.name} (${platform.osVersion()})"
}
// androidMain
import android.os.Build
class AndroidPlatform : Platform {
override val name: String = "Android"
override fun osVersion(): String = Build.VERSION.RELEASE
}
// iosMain
import platform.UIKit.UIDevice
class IOSPlatform : Platform {
override val name: String = UIDevice.currentDevice.systemName()
override fun osVersion(): String = UIDevice.currentDevice.systemVersion
}
Sometimes you do not want a class. Use expect fun to declare, actual fun to implement.
// commonMain
expect fun currentTimeMillis(): Long
// androidMain
import android.os.SystemClock
actual fun currentTimeMillis(): Long = SystemClock.elapsedRealtime()
// iosMain
import platform.Foundation.NSDate
import platform.Foundation.timeIntervalSince1970
actual fun currentTimeMillis(): Long =
(NSDate().timeIntervalSince1970 * 1000).toLong()
The compiler checks that every target has an actual. If you add a new target, you must supply it.
Diagram: commonMain declares the contract; each platform source set supplies an actual implementation.
Loading diagram...
| Need | Use |
|---|---|
| Share a class with platform behavior | Interface + per-platform impl |
| Single platform-only function | expect fun / actual fun |
| Platform-only property | expect val / actual val |
Prefer interfaces for testability. You can pass a fake Platform in tests.
Calling Greeting(AndroidPlatform()).greet():
Running on Android (15)
Calling Greeting(IOSPlatform()).greet():
Running on iOS (18.0)
commonMain, define an interface Logger with fun log(message: String).androidMain, implement it with android.util.Log.d.iosMain, implement it with println (or NSLog).expect fun platformName(): String and an actual in both source sets.platformName() using your Logger.