Help us improve
Share bugs, ideas, or general feedback.
From kmp-migration
Migrates Kotlin Multiplatform projects from the old single-module (composeApp) structure to the new shared + separate app-module layout. Handles AGP 9.0 compliance, stale IDE configurations, and project restructuring.
npx claudepluginhub cmota/kmp-migration-skill --plugin kmp-migrationHow this skill is triggered — by the user, by Claude, or both
Slash command
/kmp-migration:kmp-migrationThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Helps users migrate a Kotlin Multiplatform project from the old `composeApp` single-module
Migrates Android modules to Kotlin Multiplatform (KMP) for iOS sharing: assesses feasibility, audits dependencies, sets up source sets (commonMain/androidMain), upgrades Kotlin to 2.x, exposes iOS frameworks.
Automates DI framework migrations in Kotlin/Android/KMP projects to Koin 4.x Compiler Plugin from Hilt, Dagger, Toothpick, Kodein, Koin upgrades, DSL to Safe DSL/Annotations.
Guides native Android development with Kotlin idioms, Jetpack Compose UI, Room database, Hilt DI, Coroutines/Flow, WorkManager, Gradle KTS, Material Design 3, and Navigation Compose. Use for building or optimizing Android apps.
Share bugs, ideas, or general feedback.
Helps users migrate a Kotlin Multiplatform project from the old composeApp single-module
structure to the new shared + separate app-module structure introduced in May 2026.
The old structure used a single composeApp Gradle module that acted as both a KMP library
(holding shared code) and the application entry point for Android, desktop, and web. This
caused several problems:
iosApp folder — the other platforms were inconsistently co-locatedThe new structure separates concerns clearly. For variant configurations (native UI, server), see references/configurations.md.
Before doing anything, ask or infer:
references/configurations.mdsettings.gradle.kts and build.gradle.kts files? Reading the actual files makes the migration more accurate.composeApp after migration? If yes, inspect .idea metadata as part of cleanup.shared moduleExtract the KMP library portions from composeApp. Move all source sets (commonMain, androidMain, iosMain, desktopMain, wasmJsMain), shared dependencies, and resources. Do NOT move: com.android.application plugin, desktop application {} block, or app-specific signing/package config.
shared/build.gradle.kts skeleton:
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
alias(libs.plugins.androidLibrary) // library, not application
}
kotlin {
androidTarget()
iosX64(); iosArm64(); iosSimulatorArm64()
jvm("desktop")
wasmJs { browser() }
sourceSets {
commonMain.dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation(compose.components.resources)
}
}
}
android {
namespace = "com.example.myapp.shared"
compileSdk = libs.versions.android.compileSdk.get().toInt()
}
androidApp moduleStandard Android application module that depends on shared.
plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.kotlinAndroid)
alias(libs.plugins.composeCompiler)
}
android {
namespace = "com.example.myapp"
defaultConfig {
applicationId = "com.example.myapp"
versionCode = 1
versionName = "1.0"
}
}
dependencies {
implementation(projects.shared)
implementation(libs.androidx.activity.compose)
}
Move AndroidManifest.xml, MainActivity.kt, and res/ from composeApp/src/androidMain/. MainActivity just calls the shared App() composable.
desktopApp module (if applicable)plugins {
alias(libs.plugins.kotlinJvm)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
}
dependencies {
implementation(projects.shared)
implementation(compose.desktop.currentOs)
}
compose.desktop {
application { mainClass = "com.example.myapp.MainKt" }
}
Move main() from composeApp/src/desktopMain/ into desktopApp/src/main/kotlin/.
webApp module (if applicable)plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
}
kotlin {
wasmJs {
moduleName = "webApp"
browser { commonWebpackConfig { outputFileName = "webApp.js" } }
binaries.executable()
}
sourceSets {
commonMain.dependencies { implementation(projects.shared) }
}
}
settings.gradle.ktsinclude(":shared")
include(":androidApp")
include(":desktopApp") // if applicable
include(":webApp") // if applicable
Remove the old :composeApp include.
If composeApp still appears in Android Studio / IntelliJ, verify this file first. The IDE
will keep importing composeApp as long as settings.gradle.kts still contains:
include(":composeApp")
Replace it with only the modules that actually exist. Do not add desktopApp or webApp
unless those modules were created.
iosApp/ stays as-is structurally, but renaming the module concept from composeApp to
shared requires updating several iOS-side references. Both lowercase composeApp and
uppercase ComposeApp must be searched — the uppercase form is used as the framework
baseName and Swift import.
7a — Update the Gradle framework baseName
In shared/build.gradle.kts (or the root build file), find the XCFramework / framework
configuration and change the baseName:
// Before
kotlin {
iosX64 { binaries.framework { baseName = "ComposeApp" } }
// ...
}
// After
kotlin {
iosX64 { binaries.framework { baseName = "Shared" } }
// ...
}
7b — Update the Xcode build phase
In Xcode → Build Phases → "Compile Kotlin Framework", update the Gradle task:
./gradlew :shared:assembleXCFramework
7c — Update Swift imports
Search iosApp/ for import ComposeApp and replace with import Shared:
rg -n "ComposeApp" iosApp/ --glob '*.swift'
Update every occurrence in .swift files — typically iOSApp.swift and ContentView.swift.
7d — Update Podfile or SPM manifest (if applicable)
If using CocoaPods, update the pod name in iosApp/Podfile:
# Before
pod 'ComposeApp', :path => '../'
# After
pod 'Shared', :path => '../'
If using Swift Package Manager, update the target dependency name accordingly.
7e — Verify with xcodebuild, not just Gradle
A successful ./gradlew :shared:assembleXCFramework does not guarantee the Xcode build
works. Always verify with an actual Xcode build:
xcodebuild -project iosApp/iosApp.xcodeproj \
-scheme iosApp \
-destination 'platform=iOS Simulator,name=iPhone 15' \
build
Or simply build from Xcode IDE to catch any remaining ComposeApp references.
composeApp/ directory after verifying the build:composeApp tasksrg -n "composeApp|ComposeApp" . --hidden \
--glob 'settings.gradle.kts' \
--glob '**/*.gradle.kts' \
--glob '.idea/**' \
--glob '.github/**' \
--glob 'gradle/**' \
--glob 'iosApp/**'
Update IDE metadata and run configurations. IntelliJ / Android Studio may keep showing
composeApp even after the source migration if any of these files still reference it:
.idea/gradle.xml module entries such as $PROJECT_DIR$/composeApp.idea/runConfigurations/*.xml and .idea/workspace.xml run manager entries.idea/deploymentTargetSelector.xml entries such as runConfigName="composeApp".idea/artifacts/composeApp_*.xml generated artifact definitionsExact run configuration field mappings — update each stale config as follows:
| Field | Old value | New value |
|---|---|---|
| Android run config name | Android App.composeApp | Android App.androidApp |
| Android module field | Breeze.composeApp | Breeze.androidApp |
| Desktop config (hot reload) | composeApp [jvm, hot] | desktopApp [jvm, hot] |
| Desktop config (standard) | composeApp [jvm] | desktopApp [jvm] |
| Web config (JS) | composeApp [js] | webApp [js] |
| Web config (WASM) | composeApp [wasmJs] | webApp [wasmJs] |
| Gradle project path (desktop) | $PROJECT_DIR$/composeApp | $PROJECT_DIR$/desktopApp |
| Gradle project path (web) | $PROJECT_DIR$/composeApp | $PROJECT_DIR$/webApp |
| Desktop hot-reload task | hotRunJvm | hotRun |
| Desktop standard task | jvmRun | run |
If the IDE files are not intentionally versioned, the pragmatic shortcut is to close the IDE,
delete stale .idea/runConfigurations/*.xml, .idea/artifacts/composeApp_*.xml, and the
composeApp entries in .idea/gradle.xml, .idea/deploymentTargetSelector.xml, and
.idea/workspace.xml, then re-open and sync Gradle.
Commit updated shared IDE files if the repository tracks them, so teammates do not keep
importing or launching composeApp.
Do not end the migration immediately after editing files. Finish with a project synchronization and validation pass:
./gradlew projects and confirm the output lists the new modules and does not list
:composeApp../gradlew :shared:compileKotlinMetadata
./gradlew :androidApp:assembleDebug
./gradlew :desktopApp:compileKotlin
./gradlew :webApp:compileKotlinJs
./gradlew :webApp:compileKotlinWasmJs
./gradlew :shared:compileKotlinIosSimulatorArm64
xcodebuild (see Step 7e) in addition to the Gradle tasks above.rg -n "composeApp|ComposeApp" . --hidden --glob '!**/build/**' --glob '!**/.gradle/**' --glob '!**/.kotlin/**'
composeApp
configurations still appear. There is no reliable repository-only edit that forces an already
open IDE window to complete a Gradle sync."Cannot apply com.android.application to a multiplatform module" → AGP 9.0 error. Move the app plugin to androidApp.
Duplicate resources / composeResources not found → Keep compose.components.resources only in shared; app modules depend on projects.shared.
expect/actual not resolved → Source set names in shared must match what they were in composeApp.
iOS build fails after rename → Search for both composeApp and ComposeApp in iosApp/. The uppercase form is the framework baseName and Swift import — update baseName = "Shared" in the Gradle build file, replace import ComposeApp with import Shared in all Swift files, update any Podfile or SPM references, and verify with xcodebuild rather than Gradle alone.
composeApp still appears in Edit Configurations or the Gradle tool window → First check settings.gradle.kts; if it still has include(":composeApp"), replace it with include(":shared"), include(":androidApp"), and any created desktopApp / webApp modules. Then search .idea for composeApp. Remove or update stale entries in .idea/gradle.xml, .idea/runConfigurations/*.xml, .idea/workspace.xml, .idea/deploymentTargetSelector.xml, and .idea/artifacts/composeApp_*.xml, then re-sync Gradle.
Run configuration still launches :composeApp (or fails with "Task ':composeApp:…' not found") → IDE run configurations were not updated. Use the field mapping table in Step 8 to update each config's name, module, Gradle project path, and task name. Or delete the stale XML files and let the IDE recreate them on next sync.
Edits look correct but the IDE still shows old modules → Run File → Sync Project with
Gradle Files. If Android Studio / IntelliJ was open during the migration and still shows
stale entries after sync, close and reopen the project so it reloads settings.gradle.kts and
the updated .idea metadata.
references/configurations.md — native UI and server-module variants