Skip to Content
Course content

114: Platform-Specific Implementations with expect/actual

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've been working on this Kotlin Multiplatform project, and I hit a wall that almost every KMP developer hits pretty early on. I wanted to add a simple "About" screen that tells the user which platform they're running the app on—Android, iOS, or maybe Desktop. My first instinct was to just write a function in my commonMain source set to return the platform name.

The "It doesn't live here" problem

I started by trying to use a standard Java approach since I'm comfortable with the JVM. I wrote this in my commonMain folder:

// Inside commonMain
fun getPlatformName(): String {
    return System.getProperty("os.name") // I thought this would just work...
}

Immediately, the IDE turned red. The compiler basically told me, "I have no idea what System is." It makes sense when you think about it: commonMain is compiled to be compatible with everything. If I'm targeting iOS, there is no JVM, no System.getProperty, and certainly no Java standard library. I can't use platform-specific APIs in the common module because the common module has to be platform-agnostic.

Defining the "What" without the "How"

So, I can't write the implementation in commonMain, but I still want to be able to call getPlatformName() from my shared business logic. I need a way to tell the Kotlin compiler: "Trust me, every platform I target will eventually provide an implementation for this function, but I'm not going to tell you how it works right here."

This is where expect comes in. I'll change my function to an expect declaration. Notice that I've removed the function body entirely—it's now more of a contract than a function:

// Inside commonMain
expect fun getPlatformName(): String

Now the compiler stops complaining about the missing System class, but it gives me a new warning: Expected function 'getPlatformName' has no actual declaration in module.... Essentially, I've made a promise, and now the compiler is holding me to it. It's saying, "Okay, you told me this exists, now you actually have to go build it for every single target you've defined in your build script."

Fulfilling the promise

Now I need to head over to my platform-specific source sets. I'll start with Android. In the androidMain folder, I use the actual keyword. This is where I can finally use the JVM-specific code I wanted earlier.

// Inside androidMain
actual fun getPlatformName(): String {
    return "Android ${android.os.Build.VERSION.SDK_INT}"
}

Now for the iOS side. I'll jump over to iosMain. Since this is compiled via Kotlin/Native, I have access to Apple's frameworks. I can use UIDevice from the UIKit library without needing any imports from Java.

// Inside iosMain
import platform.UIKit.UIDevice

actual fun getPlatformName(): String {
    return UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
}

I love how this feels. I didn't have to create a complex Interface/Implementation pattern with dependency injection just to get a simple string. The expect/actual mechanism handles the "wiring" at compile time.

Using the result in common logic

The best part is that back in commonMain, I can now use getPlatformName() as if it were a regular function. The compiler knows that regardless of which platform is being built, there will be an actual implementation available.

// Inside commonMain
fun greetUser() {
    val platform = getPlatformName()
    println("Hello from $platform!")
}

If I add a new target—say, jsMain for a web app—and I forget to add the actual fun getPlatformName() there, the project simply won't compile. It's a type-safe way to ensure that your platform-specific gaps are always filled before the code ever reaches a device.




📋 Practical Task

Implementing a Platform-Specific UUID Generator

In a real-world app, you often need to generate a unique device ID. Different platforms have different ways of doing this (e.g., java.util.UUID on Android/JVM and NSUUID on iOS).

Your task is to implement a shared utility to generate a unique ID string. Follow these requirements:

  • In commonMain, create an expect fun generateDeviceId(): String.
  • In androidMain, implement the actual function using java.util.UUID.randomUUID().toString().
  • In iosMain, implement the actual function using platform.Foundation.NSUUID().UUIDString().
  • Create a common function fun getDeviceIdentity() that calls your generateDeviceId() and returns it prefixed with the string "ID: ".
Rating
0 0

There are no comments for now.

to be the first to leave a comment.