agentsclimarketplace

Kmp project structure

Skill iammohdzaki/kmp-skills/skills/create-project/kmp-project-structure

Instead of copying and pasting the same prompts into every new project, you install this repo globally into your AI assistant. The AI learns exactly how to structure an AGP 9.0+ project, how to scaffold MVI architecture, how to resolve real library versions, and how to audit your Compose UI for Material 3 compliance.

Install
npx -y skills add iammohdzaki/kmp-skills --skill kmp-project-structure

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 4 stars4 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

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.

SKILL.md

16.0 KB, as published. Nobody here has run it

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

ModuleRoleGradle Plugins
sharedAll KMP code: common, Android, Desktop, iOS actualscom.android.kotlin.multiplatform.library + org.jetbrains.kotlin.multiplatform + org.jetbrains.compose
androidAppAndroid entry point only (MainActivity)com.android.application + org.jetbrains.kotlin.android
desktopAppDesktop entry point only (main.kt)org.jetbrains.kotlin.jvm
iosAppXcode project, Swift glueN/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")   // 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.<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)
    // Note: NO kotlinMultiplatform plugin here — this is a plain Android app
    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() 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.<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() lives in shared
        App()
    }
}

shared/src/commonMain/kotlin/<pkg>/App.kt

package com.<yourpackage>

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: androidTargetandroid

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 SetLocation in shared/Contains
commonMainsrc/commonMain/kotlin/All shared business logic, shared UI, ViewModels
androidMainsrc/androidMain/kotlin/Android actual implementations, platform utils
jvmMainsrc/jvmMain/kotlin/Desktop actual implementations
iosMainsrc/iosMain/kotlin/iOS actual implementations
commonTestsrc/commonTest/kotlin/Shared unit tests

What Goes Where

CodeModuleSource Set
@Composable fun App()sharedcommonMain
AppTheme, MaterialThemesharedcommonMain
ViewModels, repositories, use-casessharedcommonMain
expect/actual Platform APIssharedcommonMain / androidMain / jvmMain
DI modules (Koin)sharedcommonMain (shared) + androidMain/jvmMain (platform)
Navigation (NavHost)sharedcommonMain
MainActivity (Android entry point)androidAppmain
main.kt (Desktop entry point)desktopAppmain
Application class (if Koin on Android)androidAppmain

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.ktscom.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

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.