Step 6 of 7
Compose Multiplatform overview, sharing UI across platforms, navigation
Compose Multiplatform — แชร์ UI ข้าม Android, iOS, desktop
Compose Multiplatform extends Jetpack Compose to share UI across Android, iOS, desktop, and web. You write the UI once in Kotlin. Where you need a platform feature, you use expect/actual or interop.
Version: Compose Multiplatform 1.11.x on Kotlin 2.4.x.
// shared/composeApp/build.gradle.kts (plugins)
plugins {
kotlin("multiplatform")
kotlin("plugin.compose") version "2.4.10"
id("org.jetbrains.compose") version "1.11.0"
}
On Kotlin 2.x the kotlin("plugin.compose") plugin supplies the Compose Compiler. The org.jetbrains.compose plugin supplies the multiplatform Compose libraries.
kotlin {
androidTarget()
iosArm64(); iosX64(); iosSimulatorArm64()
sourceSets {
commonMain.dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation("org.jetbrains.androidx.navigation:navigation-compose:2.9.0")
}
}
}
Write the UI in commonMain. The same code runs on every target.
// commonMain
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun CounterApp() {
var count by remember { mutableStateOf(0) }
Column(Modifier.padding(16.dp)) {
Text("Count: $count")
Button(onClick = { count++ }) {
Text("Increment")
}
}
}
Each target calls the shared composable from its own entry point.
// androidMain
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent { CounterApp() }
}
}
// iosMain
import androidx.compose.ui.window.ComposeUIViewController
fun CounterViewController() = ComposeUIViewController { CounterApp() }
Swift wraps that UIViewController.
Use the multiplatform Navigation Compose library for shared routes.
// commonMain
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
@Composable
fun AppNav() {
val nav = rememberNavController()
NavHost(nav, startDestination = "home") {
composable("home") { HomeScreen(onNext = { nav.navigate("detail") }) }
composable("detail") { DetailScreen() }
}
}
When a small piece of UI differs (for example, a platform font or image loader), use expect/actual:
// commonMain
@Composable expect fun PlatformIcon()
// androidMain
@Composable actual fun PlatformIcon() { /* Android resource */ }
// iosMain
@Composable actual fun PlatformIcon() { /* UIKit interop */ }
Diagram: One shared composable is hosted by each platform entry point.
Loading diagram...
The button starts at Count: 0 and increases by one each tap, on Android, iOS, and desktop.
CounterApp composable with a Text and a Button.Activity and as an iOS ComposeUIViewController.