| name | kmp-project-structure |
| category | create-project |
| description | Reference skill for the new recommended KMP project structure (2026+). Covers the shift from the old single-module composeApp layout to the new multi-module shared/ + androidApp/ + desktopApp/ structure required for AGP 9.0+. Includes full file and Gradle configuration for both old and new layouts.
|
| targets | ["antigravity","claude","cursor","windsurf"] |
| sources | ["https://kotlinlang.org/docs/multiplatform/multiplatform-compatibility-guide.html","https://developer.android.com/kotlin/multiplatform/plugin","https://kmp.jetbrains.com/"] |
KMP Project Structure (2026+)
IMPORTANT — Structure Changed
The KMP project structure has changed significantly. The old single-module layout where
composeApp held both shared code and the Android app entry point is deprecated and
incompatible with AGP 9.0+.
Always scaffold new projects using the new multi-module layout described in this skill.
Use the KMP Wizard to generate a reference project if unsure.
Old vs New Structure
Old Structure (pre-2026 / AGP < 9.0) — Do NOT use for new projects
MyApp/
├── composeApp/ ← ⚠️ Mixed: shared code + Android app entry point
│ ├── build.gradle.kts ← applies com.android.application + kotlin.multiplatform
│ └── src/
│ ├── commonMain/kotlin/…
│ ├── androidMain/kotlin/… ← MainActivity lives here
│ └── jvmMain/kotlin/…
├── iosApp/ ← Xcode project
├── gradle/libs.versions.toml
├── build.gradle.kts
└── settings.gradle.kts
Problem: AGP 9.0 forbids com.android.application (or com.android.library) from
coexisting with org.jetbrains.kotlin.multiplatform in the same module.
New Structure (AGP 9.0+ / KMP 2.2+) — Use this for all new projects
MyApp/
├── shared/ ← KMP library module (all shared code + shared UI)
│ ├── build.gradle.kts ← applies com.android.kotlin.multiplatform.library + kotlin.multiplatform
│ └── src/
│ ├── commonMain/kotlin/<pkg>/
│ │ ├── App.kt ← Root @Composable (shared UI)
│ │ ├── mvi/
│ │ ├── di/
│ │ └── navigation/
│ ├── androidMain/kotlin/<pkg>/ ← Android-specific actual implementations only
│ └── jvmMain/kotlin/<pkg>/ ← Desktop-specific actual implementations only
│
├── androidApp/ ← Thin Android shell (entry point only)
│ ├── build.gradle.kts ← applies com.android.application only
│ └── src/main/kotlin/<pkg>/
│ └── MainActivity.kt ← calls setContent { App() } — nothing more
│
├── desktopApp/ ← (optional) Thin Desktop shell
│ ├── build.gradle.kts ← applies org.jetbrains.kotlin.jvm
│ └── src/main/kotlin/
│ └── main.kt ← calls application { Window { App() } }
│
├── iosApp/ ← Xcode project (unchanged)
│ └── …
│
├── gradle/
│ └── libs.versions.toml
├── build.gradle.kts ← root build file
├── settings.gradle.kts
└── gradle.properties
Key principle: shared contains everything. App modules (androidApp, desktopApp)
are thin shells — they only wire the platform entry point to shared.
Module Responsibilities
| Module | Role | Gradle Plugins |
|---|
shared | All KMP code: common, Android, Desktop, iOS actuals | com.android.kotlin.multiplatform.library + org.jetbrains.kotlin.multiplatform + org.jetbrains.compose |
androidApp | Android entry point only (MainActivity) | com.android.application + org.jetbrains.kotlin.android |
desktopApp | Desktop entry point only (main.kt) | org.jetbrains.kotlin.jvm |
iosApp | Xcode project, Swift glue | N/A (Xcode project) |
File Contents
settings.gradle.kts
pluginManagement {
repositories {
google()
gradlePluginPortal()
mavenCentral()
}
}
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}
rootProject.name = "MyApp"
include(":shared")
include(":androidApp")
include(":desktopApp")
Root build.gradle.kts
plugins {
alias(libs.plugins.kotlinMultiplatform) apply false
alias(libs.plugins.androidApplication) apply false
alias(libs.plugins.androidKmpLibrary) apply false
alias(libs.plugins.composeMultiplatform) apply false
alias(libs.plugins.composeCompiler) apply false
alias(libs.plugins.kotlinSerialization) apply false
}
shared/build.gradle.kts
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidKmpLibrary)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
alias(libs.plugins.kotlinSerialization)
}
kotlin {
android {
compilations.all {
compileTaskProvider.configure {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
}
}
}
jvm("desktop")
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "shared"
isStatic = true
}
}
sourceSets {
commonMain.dependencies {
implementation(libs.compose.runtime)
implementation(libs.compose.foundation)
implementation(libs.compose.ui)
implementation(libs.compose.material3)
implementation(libs.compose.material.icons.core)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
implementation(libs.lifecycle.viewmodel)
}
androidMain.dependencies {
implementation(libs.compose.ui.tooling.preview)
implementation(libs.androidx.activity.compose)
}
val desktopMain by getting {
dependencies {
implementation(compose.desktop.currentOs)
}
}
commonTest.dependencies {
implementation(libs.kotlin.test)
}
}
}
android {
namespace = "com.<yourpackage>.shared"
compileSdk = libs.versions.android.compileSdk.get().toInt()
defaultConfig {
minSdk = libs.versions.android.minSdk.get().toInt()
}
}
androidApp/build.gradle.kts
plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.composeCompiler)
kotlin("android")
}
android {
namespace = "com.<yourpackage>"
compileSdk = libs.versions.android.compileSdk.get().toInt()
defaultConfig {
applicationId = "com.<yourpackage>"
minSdk = libs.versions.android.minSdk.get().toInt()
targetSdk = libs.versions.android.targetSdk.get().toInt()
versionCode = 1
versionName = "1.0"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
dependencies {
implementation(project(":shared"))
implementation(libs.androidx.activity.compose)
}
androidApp/src/main/kotlin/<pkg>/MainActivity.kt
package com.<yourpackage>
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
App()
}
}
}
desktopApp/build.gradle.kts (optional)
plugins {
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
kotlin("jvm")
}
dependencies {
implementation(project(":shared"))
implementation(compose.desktop.currentOs)
}
compose.desktop {
application {
mainClass = "com.<yourpackage>.MainKt"
nativeDistributions {
targetFormats(
org.jetbrains.compose.desktop.application.dsl.TargetFormat.Dmg,
org.jetbrains.compose.desktop.application.dsl.TargetFormat.Msi,
org.jetbrains.compose.desktop.application.dsl.TargetFormat.Deb
)
packageName = "MyApp"
packageVersion = "1.0.0"
}
}
}
desktopApp/src/main/kotlin/main.kt (optional)
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
fun main() = application {
Window(
onCloseRequest = ::exitApplication,
title = "MyApp"
) {
App()
}
}
shared/src/commonMain/kotlin/<pkg>/App.kt
package com.<yourpackage>
import androidx.compose.runtime.Composable
@Composable
fun App() {
AppTheme {
}
}
gradle/libs.versions.toml
⚠️ Use the kmp-versions skill to fill in real version numbers — do not hardcode from memory.
[versions]
kotlin = "FETCH"
agp = "FETCH"
composeMultiplatform = "FETCH"
coroutines = "FETCH"
serialization = "FETCH"
lifecycle = "FETCH"
android-compileSdk = "35"
android-targetSdk = "35"
android-minSdk = "24"
[libraries]
compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "composeMultiplatform" }
compose-foundation = { module = "org.jetbrains.compose.foundation:foundation", version.ref = "composeMultiplatform" }
compose-ui = { module = "org.jetbrains.compose.ui:ui", version.ref = "composeMultiplatform" }
compose-material3 = { module = "org.jetbrains.compose.material3:material3", version.ref = "composeMultiplatform" }
compose-material-icons-core = { module = "org.jetbrains.compose.material:material-icons-core", version.ref = "composeMultiplatform" }
compose-ui-tooling-preview = { module = "org.jetbrains.compose.ui:ui-tooling-preview", version.ref = "composeMultiplatform" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" }
lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel", version.ref = "lifecycle" }
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
androidx-activity-compose = { module = "androidx.activity:activity-compose", version = "1.10.1" }
[plugins]
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
androidApplication = { id = "com.android.application", version.ref = "agp" }
androidKmpLibrary = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" }
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" }
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
DSL Migration: androidTarget → android
With Kotlin ≥ 2.3.0 and the com.android.kotlin.multiplatform.library plugin, the Android
target DSL block is renamed:
kotlin {
androidTarget {
compilations.all { … }
}
}
kotlin {
android {
compilations.all { … }
}
}
Source Set Naming
Source set names are unchanged — only the Gradle module structure changed:
| Source Set | Location in shared/ | Contains |
|---|
commonMain | src/commonMain/kotlin/ | All shared business logic, shared UI, ViewModels |
androidMain | src/androidMain/kotlin/ | Android actual implementations, platform utils |
jvmMain | src/jvmMain/kotlin/ | Desktop actual implementations |
iosMain | src/iosMain/kotlin/ | iOS actual implementations |
commonTest | src/commonTest/kotlin/ | Shared unit tests |
What Goes Where
| Code | Module | Source Set |
|---|
@Composable fun App() | shared | commonMain |
AppTheme, MaterialTheme | shared | commonMain |
| ViewModels, repositories, use-cases | shared | commonMain |
expect/actual Platform APIs | shared | commonMain / androidMain / jvmMain |
| DI modules (Koin) | shared | commonMain (shared) + androidMain/jvmMain (platform) |
Navigation (NavHost) | shared | commonMain |
MainActivity (Android entry point) | androidApp | main |
main.kt (Desktop entry point) | desktopApp | main |
Application class (if Koin on Android) | androidApp | main |
Migration Checklist (old → new structure)
Official References