# Kmp Project Structure

> >

- **Type:** Skill
- **Install:** `agentstack add skill-iammohdzaki-kmp-skills-kmp-project-structure`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [iammohdzaki](https://agentstack.voostack.com/s/iammohdzaki)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [iammohdzaki](https://github.com/iammohdzaki)
- **Source:** https://github.com/iammohdzaki/kmp-skills/tree/main/skills/create-project/kmp-project-structure

## Install

```sh
agentstack add skill-iammohdzaki-kmp-skills-kmp-project-structure
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## 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](https://kmp.jetbrains.com/) 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`

```kotlin
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`

```kotlin
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`

```kotlin
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`

```kotlin
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)

```kotlin
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)

```kotlin
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`

```kotlin
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.

```toml
[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:

```kotlin
// ❌ 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 all `composeApp/src/` contents here
- [ ] Create `androidApp/` module — move `MainActivity` + `Application` here
- [ ] Update `shared/build.gradle.kts` — replace `com.android.library` with `com.android.kotlin.multiplatform.library`
- [ ] Rename `androidTarget {}` to `android {}` in `shared/build.gradle.kts`
- [ ] Update `androidApp/build.gradle.kts` — `com.android.application` only, depend on `:shared`
- [ ] Update `settings.gradle.kts` — include `:shared`, `:androidApp`, `:desktopApp`
- [ ] Remove `compose.*` plugin accessor shorthands — replace with `libs.*`
- [ ] Add `material-icons-core` explicitly (no longer transitive since CMP 1.8.2)
- [ ] Update all `import` statements for moved classes

---

## Official References

- [KMP Wizard](https://kmp.jetbrains.com/) — generates a valid project in the new structure
- [Android KMP Library Plugin](https://developer.android.com/kotlin/multiplatform/plugin)
- [AGP 9 Migration Guide](https://kotlinlang.org/docs/multiplatform/multiplatform-project-agp-9-migration.html)
- [KMP Compatibility Guide](https://kotlinlang.org/docs/multiplatform/multiplatform-compatibility-guide.html)
- [New Default KMP Structure (JetBrains Blog)](https://blog.jetbrains.com/kotlin/2026/05/new-kmp-default-structure/)

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [iammohdzaki](https://github.com/iammohdzaki)
- **Source:** [iammohdzaki/kmp-skills](https://github.com/iammohdzaki/kmp-skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-iammohdzaki-kmp-skills-kmp-project-structure
- Seller: https://agentstack.voostack.com/s/iammohdzaki
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
