-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Null Safety
-
Section 4: Object-Oriented Kotlin
-
Section 5: Functional Kotlin
-
Section 6: Coroutines
-
Section 7: Collections Deep Dive
-
Section 8: Type System Deep Dive
-
Section 9: Interop and Tooling
-
Section 10: Kotlin DSLs and Patterns
-
Section 11: Testing and Quality
-
Section 12: Server-Side Kotlin
-
Section 13: Practical Projects
-
Section 14: Interview Practice
-
Section 15: More Practice Exercises
-
Section 16: More Standard Library
-
Section 17: Multiplatform Kotlin
-
Section 18: kotlin.collections In Depth
-
Section 19: kotlin.text In Depth
-
Section 20: kotlin.ranges and kotlin.sequences
-
Section 21: kotlin.io and File Handling
-
Section 22: kotlinx.coroutines Deep Dive
-
Section 23: kotlin.reflect
-
Section 24: Android Development with Kotlin Overview
-
Section 25: Kotlin Multiplatform Deep Dive
-
Section 26: Kotlin for Backend Deep Dive
-
Section 27: Kotlin Design Patterns
-
Section 28: Advanced Language Features
-
Section 29: More Practice Exercises
-
Section 30: More Interview Practice
-
Section 31: Kotlin Type System Deep Dive
-
Section 32: Kotlin Null Safety Advanced
-
Section 33: Kotlin Testing Deep Dive
-
Section 34: Kotlin Build Tooling Deep Dive
-
Section 35: Kotlin Serialization
-
Section 36: Kotlin Performance Considerations
-
Section 37: Kotlin Native Overview
-
Section 38: Kotlin for Data and Scripting
-
Section 39: More Coroutines Practice
-
Section 40: More Android-Adjacent Patterns
-
Section 41: More Practical Projects
-
Section 42: More Design and Architecture Practice
-
Section 43: Kotlin Language Evolution
-
Section 44: More Interview and Review
-
Section 45: Kotlin Delegation Patterns Deep Dive
-
Section 46: Kotlin Annotations Deep Dive
-
Section 47: Kotlin for Gradle Plugin Development
-
Section 48: Kotlin Concurrency Beyond Coroutines
-
Section 49: Kotlin Compiler Internals
-
Section 50: Real-World Kotlin Case Studies
-
Section 51: Final Practice Projects
-
Section 52: Kotlin for Server-Side Reactive Programming
-
Section 53: More Practice and Drills
-
Section 54: Kotlin Security Practices
114: Platform-Specific Implementations with expect/actual
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 anexpect fun generateDeviceId(): String. - In
androidMain, implement theactualfunction usingjava.util.UUID.randomUUID().toString(). - In
iosMain, implement theactualfunction usingplatform.Foundation.NSUUID().UUIDString(). - Create a common function
fun getDeviceIdentity()that calls yourgenerateDeviceId()and returns it prefixed with the string"ID: ".
There are no comments for now.