Compose Multiplatform (CMP) allows you to share your declarative UI code across multiple platforms, including Android, iOS, desktop, and web. In this comprehensive guide, we will walk you through setting up a Compose Multiplatform project using Jetpack Compose. This involves creating a Kotlin Multiplatform project, configuring dependencies, and structuring your codebase for efficient cross-platform UI development.
What is Compose Multiplatform?
Compose Multiplatform (CMP) is a declarative UI framework based on Kotlin and Jetpack Compose, allowing you to write UI code once and deploy it on multiple platforms like Android, iOS, desktop (JVM), and web. CMP promotes code reuse and simplifies UI development across diverse ecosystems.
Why Use Compose Multiplatform?
- Code Reusability: Write UI code once and reuse it across different platforms.
- Consistent UI: Maintain a consistent look and feel across all supported platforms.
- Modern Declarative UI: Leverage Jetpack Compose’s declarative approach for efficient UI development.
- Kotlin Advantage: Benefit from Kotlin’s conciseness, safety, and interoperability with platform-specific code.
Prerequisites
Before starting, make sure you have the following:
- IntelliJ IDEA or Android Studio: Latest version installed.
- Kotlin Plugin: Kotlin plugin installed and enabled in your IDE.
- JDK: Java Development Kit (JDK) 11 or higher.
- Android SDK: Required if you plan to target Android.
- Xcode: Required for targeting iOS.
Step-by-Step Guide to Setting Up a Compose Multiplatform Project
Step 1: Create a New Kotlin Multiplatform Project
- Open IntelliJ IDEA or Android Studio.
- Click on
New Project. - Choose
Kotlinin the left panel and selectMultiplatform App. - Click
Next. - Provide the project name, location, and desired settings.
- Target Platforms: Select Android, iOS, and Desktop (JVM). You can also add other targets as needed.
- Click
Create.
After creating the project, Gradle will sync and generate the initial project structure.
Step 2: Project Structure
A typical Compose Multiplatform project structure includes:
commonMain: Contains the common code shared between all platforms. UI components, business logic, and data models go here.androidMain: Contains Android-specific code.iosMain: Contains iOS-specific code.desktopMain: Contains JVM-specific code for desktop applications.jsMain: Contains JavaScript-specific code for web applications.
MyCMPProject/
├── .gradle/
├── .idea/
├── androidApp/
│ ├── src/
│ │ └── androidMain/
│ │ ├── AndroidManifest.xml
│ │ └── kotlin/
├── iosApp/
│ ├── iosApp/
│ ├── ...
├── desktopApp/
│ ├── src/
│ │ └── jvmMain/
│ │ └── kotlin/
├── common/
│ ├── src/
│ │ └── commonMain/
│ │ └── kotlin/
├── build.gradle.kts
└── settings.gradle.kts
Step 3: Configure Dependencies
Update your build.gradle.kts file with the necessary Compose Multiplatform dependencies.
- Open
build.gradle.ktsin the root of your project. - Add the Compose Multiplatform plugin and dependencies.
plugins {
kotlin("multiplatform") version "1.9.22" // Replace with the latest version
id("org.jetbrains.compose") version "1.6.0" // Replace with the latest version
}
group = "org.example"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
google()
}
kotlin {
androidTarget {
compilations.all {
kotlinOptions {
jvmTarget = "11"
}
}
}
iosX64()
iosArm64()
iosSimulatorArm64()
jvm("desktop")
sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.components.resources)
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
implementation(compose.ui)
}
}
val androidMain by getting {
dependsOn(commonMain)
dependencies {
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
implementation("androidx.activity:activity-compose:1.8.2")
implementation(compose.uiTooling)
implementation(compose.uiToolingPreview)
}
}
val iosX64Main by getting
val iosArm64Main by getting
val iosSimulatorArm64Main by getting {
dependsOn(iosX64Main)
}
val iosMain by getting {
dependsOn(commonMain)
iosX64Main.dependsOn(this)
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
dependencies {
implementation(compose.uikit.uikit)
implementation(compose.uikit.uikitCompose)
}
}
val desktopMain by getting {
dependsOn(commonMain)
dependencies {
implementation(compose.desktop.desktop)
}
}
}
}
android {
namespace = "org.example.MyCMPProject.androidApp"
compileSdk = 34
defaultConfig {
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.1" // Replace with the latest version
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
Remember to replace 1.9.22 and 1.6.0 with the latest versions of the Kotlin and Compose plugins, respectively. Ensure that Compose compiler extension version is compatible with the compose version that you’re using. You may also need to add platform-specific configurations based on your project’s requirements.
Step 4: Create a Simple UI
Let’s create a simple UI that displays “Hello, Compose Multiplatform!” in the commonMain source set.
- Navigate to
common/src/commonMain/kotlin. - Create a new Kotlin file named
App.kt.
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.layout.*
import androidx.compose.ui.platform.testTag
@Composable
fun App() {
MaterialTheme {
Column(
modifier = Modifier.fillMaxSize().padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Hello, Compose Multiplatform!", modifier = Modifier.testTag("greeting"))
}
}
}
This code defines a simple Compose UI with a Text composable.
Step 5: Integrate with Platform-Specific Code
Now, let’s integrate this common UI with the platform-specific code for Android, iOS, and desktop.
Android Integration
- Navigate to
androidApp/src/androidMain/kotlin. - Open or create
MainActivity.kt.
package org.example.MyCMPProject.androidApp
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import org.example.App
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
App()
}
}
}
iOS Integration
For iOS, you need to integrate the Compose UI within your Swift code.
- Open the
iosAppproject in Xcode. - Add a new Swift file named
ComposeViewController.swift.
import UIKit
import SwiftUI
import Compose
class ComposeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let rootView = ComposeView()
rootView.setContent {
AppKt.App()
}
let hostingController = UIHostingController(rootView: rootView)
addChild(hostingController)
view.addSubview(hostingController.view)
hostingController.didMove(toParent: self)
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
hostingController.view.topAnchor.constraint(equalTo: view.topAnchor),
hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
}
-
In your
MainViewController.swift, present theComposeViewController. -
Update your
MainViewController.swift
import UIKit
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(type: .system)
button.setTitle("Compose Screen", for: .normal)
button.addTarget(self, action: #selector(openComposeView), for: .touchUpInside)
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
@objc func openComposeView() {
let composeViewController = ComposeViewController()
self.present(composeViewController, animated: true, completion: nil)
}
}
Desktop Integration
- Navigate to
desktopApp/src/jvmMain/kotlin. - Open or create
Main.kt.
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import org.example.App
fun main() = application {
Window(title = "Compose Multiplatform Desktop") {
App()
}
}
Step 6: Run the Applications
- Android: Run the
androidAppon an Android emulator or device. - iOS: Run the
iosAppon an iOS simulator or device via Xcode. - Desktop: Run the
desktopAppdirectly from IntelliJ IDEA.
You should see the “Hello, Compose Multiplatform!” text on all three platforms, confirming that your setup is correct.
Tips and Best Practices
- Directory Structure: Organize your code efficiently to separate common and platform-specific logic.
- UI Abstraction: Abstract UI components to maximize code sharing while allowing platform-specific customizations.
- Dependency Management: Keep dependencies updated to leverage the latest features and bug fixes.
- Platform-Specific Code: Use
expect/actualto handle platform-specific implementations while maintaining a common API. - Testing: Implement thorough testing for each platform to ensure code quality and consistency.
Conclusion
Setting up a Compose Multiplatform project in Jetpack Compose allows you to efficiently share UI code across Android, iOS, and desktop platforms. By following this guide, you can create a structured and maintainable project, enabling you to deliver consistent and engaging user experiences across diverse ecosystems. Embrace Compose Multiplatform to unlock the full potential of cross-platform UI development.