Skip to main content
Lumo UI fully supports Kotlin Multiplatform, allowing you to share UI code across Android, iOS, Desktop (JVM), and Web (JS/Wasm).

Prerequisites

  • Android Studio Hedgehog (2023.1.1) or later
  • Kotlin 2.1.0 or later
  • For iOS: Xcode 14.0 or later (macOS only)
  • For Desktop: JDK 17 or later
  • Minimum SDK 24 for Android

Platform Support

Lumo UI supports the following platforms:
  • Android (androidTarget)
  • iOS (iosArm64, iosX64, iosSimulatorArm64)
  • macOS (macosX64, macosArm64)
  • Desktop (JVM)
  • Web (JS and Wasm)

Project Setup

1

Create Multiplatform Project

Create a new Kotlin Multiplatform project or open an existing one. Your project structure should include:
your-multiplatform-app/
├── common/                 # Shared common module
├── android/               # Android runner
├── desktop/               # Desktop runner
├── web/                   # Web runner
├── ios/                   # iOS configuration
└── ui-components/         # Shared UI components
2

Configure Version Catalog

Add multiplatform dependencies to gradle/libs.versions.toml:
gradle/libs.versions.toml
[versions]
compileSdk = "35"
minSdk = "24"
targetSdk = "35"

kotlin = "2.1.0"
compose-multiplatform = "1.7.3"
androidx-activity-compose = "1.9.3"
androidx-lifecycle-compose = "2.8.4"
androidx-multiplatform-navigation = "2.8.0-alpha12"
nomanr-composables = "1.1.0"

[libraries]
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity-compose" }
androidx-lifecycle-runtime-multiplatform = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle-compose" }
androidx-multiplatform-navigation = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "androidx-multiplatform-navigation" }
nomanr-composables = { group = "com.nomanr", name = "composables", version.ref = "nomanr-composables" }

[plugins]
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
android-library = { id = "com.android.library", version.ref = "android-gradle-plugin" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
compose-multiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
3

Configure UI Components Module

Create a shared UI components module with multiplatform support:
ui-components/build.gradle.kts
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
    alias(libs.plugins.kotlin.multiplatform)
    alias(libs.plugins.android.library)
    alias(libs.plugins.compose.compiler)
    alias(libs.plugins.compose.multiplatform)
}

kotlin {
    // Android target
    androidTarget {
        compilerOptions {
            jvmTarget.set(JvmTarget.JVM_11)
        }
    }

    // iOS targets
    iosArm64()
    iosX64()
    iosSimulatorArm64()
    
    // macOS targets
    macosX64()
    macosArm64()

    // Desktop target
    jvm("desktop")

    // Web targets
    @OptIn(ExperimentalWasmDsl::class)
    wasmJs {
        browser()
    }
    js(IR)

    sourceSets {
        // Common dependencies for all platforms
        commonMain.dependencies {
            api(compose.runtime)
            api(compose.foundation)
            api(compose.material3)
            api(compose.ui)
            api(compose.components.resources)
            api(compose.components.uiToolingPreview)
            api(libs.androidx.lifecycle.runtime.multiplatform)
            api(libs.nomanr.composables)
        }

        // Android-specific dependencies
        androidMain.dependencies {
            implementation(compose.preview)
            implementation(libs.androidx.activity.compose)
        }
    }
}

android {
    namespace = "com.example.yourapp.ui"
    compileSdk = libs.versions.compileSdk.get().toInt()

    defaultConfig {
        minSdk = libs.versions.minSdk.get().toInt()
        targetSdk = libs.versions.targetSdk.get().toInt()
    }
    
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
    
    packaging {
        resources {
            excludes += "/META-INF/{AL2.0,LGPL2.1}"
        }
    }
}
4

Configure Common Module

Set up your shared common module with app logic:
common/build.gradle.kts
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
    alias(libs.plugins.kotlin.multiplatform)
    alias(libs.plugins.android.library)
    alias(libs.plugins.compose.compiler)
    alias(libs.plugins.compose.multiplatform)
    alias(libs.plugins.kotlin.serialization)
}

kotlin {
    androidTarget {
        compilerOptions {
            jvmTarget.set(JvmTarget.JVM_11)
        }
    }

    jvm("desktop")

    @OptIn(ExperimentalWasmDsl::class)
    wasmJs {
        browser()
    }
    js(IR)

    listOf(
        iosX64(),
        iosArm64(),
        iosSimulatorArm64(),
        macosX64(),
        macosArm64(),
    ).forEach { iosTarget ->
        iosTarget.binaries.framework {
            baseName = "ComposeApp"
            isStatic = true
        }
    }

    sourceSets {
        val desktopMain by getting

        androidMain {
            dependencies {
                implementation(libs.androidx.compose.ui.tooling.preview)
                implementation(libs.androidx.activity.compose)
            }
        }
        
        commonMain.dependencies {
            api(project(":ui-components"))
            implementation(compose.components.resources)
            implementation(libs.androidx.multiplatform.navigation)
            implementation(libs.kotlin.serialization.json)
            implementation(compose.materialIconsExtended)
        }
        
        desktopMain.dependencies {
            implementation(compose.desktop.currentOs)
        }
    }
}

android {
    namespace = "com.example.yourapp"
    compileSdk = libs.versions.compileSdk.get().toInt()

    defaultConfig {
        minSdk = libs.versions.minSdk.get().toInt()
        targetSdk = libs.versions.targetSdk.get().toInt()
    }
    
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
    
    packaging {
        resources {
            excludes += "/META-INF/{AL2.0,LGPL2.1}"
        }
    }
}

dependencies {
    debugImplementation(libs.androidx.compose.ui.tooling)
}
5

Create Shared UI Code

Create your shared composables in the common source set:
commonMain/kotlin/App.kt
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.nomanr.lumo.ui.AppTheme
import com.nomanr.lumo.ui.components.*

@Composable
fun App() {
    AppTheme {
        Scaffold(
            modifier = Modifier.fillMaxSize()
        ) { innerPadding ->
            MainContent(
                modifier = Modifier
                    .fillMaxSize()
                    .padding(innerPadding)
            )
        }
    }
}

@Composable
fun MainContent(modifier: Modifier = Modifier) {
    var text by remember { mutableStateOf("") }

    Column(
        modifier = modifier.padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "Hello Multiplatform!",
            style = AppTheme.typography.h1
        )

        TextField(
            value = text,
            onValueChange = { text = it },
            placeholder = { Text("Enter text") },
            label = { Text("Input") }
        )

        Button(
            text = "Click Me",
            variant = ButtonVariant.Primary,
            onClick = { /* Handle click */ }
        )
    }
}
6

Platform-Specific Configuration

Configure the Android module:
android/build.gradle.kts
plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
    alias(libs.plugins.compose.compiler)
}

android {
    namespace = "com.example.yourapp.android"
    compileSdk = libs.versions.compileSdk.get().toInt()

    defaultConfig {
        applicationId = "com.example.yourapp"
        minSdk = libs.versions.minSdk.get().toInt()
        targetSdk = libs.versions.targetSdk.get().toInt()
        versionCode = 1
        versionName = "1.0"
    }
}

dependencies {
    implementation(project(":common"))
}
7

Build and Run

./gradlew :android:installDebug

Source Set Structure

common/src/
├── commonMain/kotlin/          # Shared code for all platforms
├── androidMain/kotlin/         # Android-specific code
├── iosMain/kotlin/            # iOS-specific code
├── desktopMain/kotlin/        # Desktop-specific code
└── jsMain/kotlin/             # Web-specific code

Platform-Specific Components

Some components have platform-specific implementations:
// commonMain - Interface
expect fun getPlatformName(): String

// androidMain - Implementation
actual fun getPlatformName(): String = "Android"

// iosMain - Implementation  
actual fun getPlatformName(): String = "iOS"

Troubleshooting

Make sure you’ve generated the iOS framework:
./gradlew :common:embedAndSignAppleFrameworkForXcode
Check Xcode’s Build Phases includes a script to build the framework.
Ensure consistent JVM targets across modules:
compilerOptions {
    jvmTarget.set(JvmTarget.JVM_11)
}
Verify your main class is correctly specified:
compose.desktop {
    application {
        mainClass = "MainKt"  // Must match your actual main function file
    }
}
Make sure you’re using experimental Wasm DSL:
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
    browser()
}

Next Steps

Theming

Learn how to customize your multiplatform theme

Navigation

Set up multiplatform navigation

Components

Explore all available components

Android Setup

Android-only setup guide

Build docs developers (and LLMs) love