Skip to content

Compose Multiplatform

UI & GraphicsUI FrameworkKotlin

What it is

JetBrains' extension of Jetpack Compose to desktop, iOS and web, letting one declarative UI codebase target several platforms.

Shared composables live in commonMain. Platform-specific behaviour is supplied through expect/actual declarations.

Installation

plugins { id("org.jetbrains.compose") version "1.7.0" }

Getting started

The smallest useful thing you can do with it, and what each part means.

Shared UI with platform hooks
// commonMain — runs everywhere
@Composable
fun App(viewModel: BookViewModel) {
    MaterialTheme {
        val state by viewModel.state.collectAsState()
        BookList(state.books, onSelect = viewModel::select)
    }
}

// commonMain — declare what each platform must provide
expect fun openUrl(url: String)

// androidMain
actual fun openUrl(url: String) {
    context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
}

// iosMain
actual fun openUrl(url: String) {
    UIApplication.sharedApplication.openURL(NSURL(string = url))
}
expect/actual is the seam: shared code declares what it needs, and each platform supplies it. Anything not behind that seam has to compile everywhere.

Advanced usage

Where the library earns its place over a simpler alternative.

Entry points per platform
// desktopMain
fun main() = application {
    Window(onCloseRequest = ::exitApplication, title = "Library") {
        App(remember { BookViewModel() })
    }
}

// iosMain — embedded in a SwiftUI view
fun MainViewController() = ComposeUIViewController { App(viewModel) }

// Adapt layout to the window, not the platform.
@Composable
fun AdaptiveLayout(windowSize: DpSize) {
    if (windowSize.width > 840.dp) TwoPaneLayout() else SinglePaneLayout()
}
Branching on window size rather than platform is what makes the shared UI genuinely reusable — a phone in landscape and a small desktop window want the same layout.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

Expected declaration has no actual for target
Every target needs an actual. Add one per source set, even if it throws NotImplementedError for now.
A JVM-only library fails to resolve in commonMain
commonMain can only use Multiplatform libraries. Move the usage to a platform source set behind expect/actual.

Best practices

  • Share the layer beneath the UI first; sharing UI is the more ambitious step.
  • Branch on window size, not on platform, so layouts adapt rather than fork.
  • Expect a less mature iOS story than Android — test on device early.
  • Keep platform APIs behind expect/actual rather than scattering conditionals.

Background

Why it exists, and what it was reacting to.

Compose Multiplatform takes Google's Android UI toolkit and renders it with Skia on other platforms, so the same composables run on Android, iOS, desktop JVM and the browser.