Install
$ agentstack add skill-iammohdzaki-kmp-skills-kmp-project-structure ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
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 /
│ │ ├── App.kt ← Root @Composable (shared UI) │ │ ├── mvi/ │ │ ├── di/ │ │ └── navigation/ │ ├── androidMain/kotlin// ← Android-specific actual implementations only │ └── jvmMain/kotlin// ← Desktop-specific actual implementations only │ ├── androidApp/ ← Thin Android shell (entry point only) │ ├── build.gradle.kts ← applies com.android.application only │ └── src/main/kotlin// │ └── 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`
```kotlin
pluginManagement {
repositories {
google()
gradlePluginPortal()
mavenCentral()
}
}
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}
rootProject.name = "MyApp"
include(":shared")
include(":androidApp")
include(":desktopApp") // remove if Desktop not needed
Root build.gradle.kts
plugins {
// Declare plugins here but don't apply them at root level
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) // com.android.kotlin.multiplatform.library
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
alias(libs.plugins.kotlinSerialization)
}
kotlin {
// Android target — uses the new android{} block (NOT androidTarget{})
android {
compilations.all {
compileTaskProvider.configure {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
}
}
}
// Desktop target (optional)
jvm("desktop")
// iOS targets (optional — add only if targeting iOS)
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "shared"
isStatic = true
}
}
sourceSets {
commonMain.dependencies {
// Compose Multiplatform — use libs.* (compose.* accessors deprecated)
implementation(libs.compose.runtime)
implementation(libs.compose.foundation)
implementation(libs.compose.ui)
implementation(libs.compose.material3)
implementation(libs.compose.material.icons.core)
// Kotlin
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
implementation(libs.lifecycle.viewmodel)
// Add DI, navigation, image loading here based on user choice
}
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)
}
}
}
// AGP 9+ — configure via android {} block from com.android.kotlin.multiplatform.library
android {
namespace = "com..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)
// Note: NO kotlinMultiplatform plugin here — this is a plain Android app
kotlin("android")
}
android {
namespace = "com."
compileSdk = libs.versions.android.compileSdk.get().toInt()
defaultConfig {
applicationId = "com."
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//MainActivity.kt
package com.
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() lives in shared — androidApp knows nothing about screens
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..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() lives in shared
App()
}
}
shared/src/commonMain/kotlin//App.kt
package com.
import androidx.compose.runtime.Composable
@Composable
fun App() {
AppTheme {
// AppNavigation() or first screen here
}
}
gradle/libs.versions.toml
> ⚠️ Use the kmp-versions skill to fill in real version numbers — do not hardcode from memory.
[versions]
# Fetch versions from official sources — see kmp-versions skill
kotlin = "FETCH"
agp = "FETCH"
composeMultiplatform = "FETCH"
coroutines = "FETCH"
serialization = "FETCH"
lifecycle = "FETCH"
android-compileSdk = "35"
android-targetSdk = "35"
android-minSdk = "24"
[libraries]
# Compose Multiplatform — explicit declarations (compose.* accessors deprecated in CMP 1.10)
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" }
# Kotlin / KMP
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" }
# Android
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" }
# AGP 9+ new plugin for shared KMP library module
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:
// ❌ Old — deprecated since Kotlin 2.3.0, warns; removed in future
kotlin {
androidTarget {
compilations.all { … }
}
}
// ✅ New — use android {} when using com.android.kotlin.multiplatform.library
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)
- [ ] Create
shared/module — move allcomposeApp/src/contents here - [ ] Create
androidApp/module — moveMainActivity+Applicationhere - [ ] Update
shared/build.gradle.kts— replacecom.android.librarywithcom.android.kotlin.multiplatform.library - [ ] Rename
androidTarget {}toandroid {}inshared/build.gradle.kts - [ ] Update
androidApp/build.gradle.kts—com.android.applicationonly, depend on:shared - [ ] Update
settings.gradle.kts— include:shared,:androidApp,:desktopApp - [ ] Remove
compose.*plugin accessor shorthands — replace withlibs.* - [ ] Add
material-icons-coreexplicitly (no longer transitive since CMP 1.8.2) - [ ] Update all
importstatements for moved classes
Official References
- KMP Wizard — generates a valid project in the new structure
- Android KMP Library Plugin
- AGP 9 Migration Guide
- KMP Compatibility Guide
- New Default KMP Structure (JetBrains Blog)
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: iammohdzaki
- Source: iammohdzaki/kmp-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.