Compose Multiplatform in Action: Using expect and actual to Implement Cross-Platform Code
Introduction
Compose Multiplatform (CMP)
By sharing code
we not only reduce duplicate work
but also improve development efficiency and code consistency
CMP
provides developers with a cross-platform solution
allowing the same business logic to run on different platforms
In this article
we'll explore how CMP
uses the expect
and actual
keywords to implement cross-platform
code
and share some practical experiences
What are expect and actual?
Let's first get a basic understanding of Compose Multiplatform and Kotlin Multiplatform
-
The
expect
keyword is used to declare a platform-dependent interface or class in shared codewhile the
actual
keyword is used to implement this interface or class on specific platforms
Let's look at a simple example
The code below shows how to return the name of the platform on each platform
In commonMain
, we use the expect
keyword to declare a function
and expect other platforms in the CMP project to implement this function
// in ~/commonMain/.../xxx.kt
expect fun getPlatformName(): String
// in ~/androidMain/.../xxx.kt
actual fun getPlatformName(): String {
return "Android"
}
// in ~/iosMain/.../xxx.kt
actual fun getPlatformName(): String {
return "iOS"
}
Additionally
expect
and actual
can be used not only for functions
but also for classes
This allows us to flexibly define platform-specific logic
in shared code
and provide implementations on specific platforms
// in ~/commonMain/.../FileSystem.kt
expect class FileSystem {
fun readFile(path: String): String
}
// in ~/androidMain/.../FileSystem.kt
actual class FileSystem {
actual fun readFile(path: String): String {
// Android-specific file reading logic
}
}
// in ~/iosMain/.../FileSystem.kt
actual class FileSystem {
actual fun readFile(path: String): String {
// iOS-specific file reading logic
}
}
-
For instance, when setting up the material 3 theme a couple of days ago
we set up an
expect
function called setStatusBarStylemainly
expecting a cross-platform function
that can set the status bar on specific platforms
// in ~/commonMain/.../xxxx.kt
@Composable
expect fun setStatusBarStyle(
backgroundColor: Color,
isDarkTheme: Boolean
)
// in ~/androidMain/.../xxx.kt
@Composable
actual fun setStatusBarStyle(backgroundColor: Color, isDarkTheme: Boolean) {
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = backgroundColor.toArgb()
window.navigationBarColor = Color.Transparent.toArgb()
}
}
}
// in ~/iosMain/.../xxx.kt
@Composable
actual fun setStatusBarStyle(backgroundColor: Color, isDarkTheme: Boolean) {
DisposableEffect(isDarkTheme) {
val statusBarStyle = if (isDarkTheme) {
UIStatusBarStyleLightContent
} else {
UIStatusBarStyleDarkContent
}
UIApplication.sharedApplication.setStatusBarStyle(statusBarStyle, animated = true)
onDispose { }
}
}
-
A second example is when using Koin for dependency injection on specific platforms
When a platform needs some specific
instances
you might need to implement them on that platform
In this case, you can create an
expect
Koin module in commonMain
Here's an example (we'll cover how to use Koin in more detail later)
// in ~/commonMain/.../xxxx.kt
expect val platformModule: Module
fun appModule() =
listOf(platformModule)
// in ~/androidMain/.../xxx.kt
actual val platformModule: Module = module {
/** Datastore*/
single { dataStore(get<Context>()) }
/** Database*/
single<RoomDatabase.Builder<AppDatabase>> {
getAppDatabase(get())
}
}
// in ~/iosMain/.../xxx.kt
actual val platformModule: Module = module {
/** DataStore*/
single { dataStore() }
/** Database*/
single { getAppDatabase() }
}
Tips
The IDE provides a way
to help you more easily identify which platform a class
is implemented for
When you write the expect
keyword in shared code
if other platforms haven't implemented the corresponding actual
The IDE will notify you that implementation is missing
At this point, you can simply hit the auto-generate button
and the IDE will create the missing implementation files
adding corresponding strings
to the file names for specific platforms
such as xxx.android.kt or xxx.ios.kt.
This way
you can more intuitively understand which platform each class belongs to
For example:
When you use command+f
to search for a function you just implemented
you can clearly know which platform's class you're currently looking at.