# Splitting a KMP + Android Application Module for AGP 9.0 AGP 9.0 does not support `com.android.application` combined with `org.jetbrains.kotlin.multiplatform` in the same module. You must split the monolithic `composeApp` module into a pure Android application module and a KMP shared library module. --- ## Old Structure (AGP 8.x) ``` composeApp/ build.gradle.kts # com.android.application + kotlin.multiplatform src/ commonMain/kotlin/ # Shared KMP code androidMain/kotlin/ # Android-specific code + MainActivity androidMain/res/ # Android resources androidMain/AndroidManifest.xml iosMain/kotlin/ # iOS-specific code desktopMain/kotlin/ # Desktop entry point (optional) ``` Single `composeApp/build.gradle.kts`: ```kotlin plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.androidApplication) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.composeCompiler) } kotlin { androidTarget { compilations.all { kotlinOptions { jvmTarget = "11" } } } iosX64() iosArm64() iosSimulatorArm64() listOf(iosX64(), iosArm64(), iosSimulatorArm64()).forEach { it.binaries.framework { baseName = "ComposeApp" isStatic = true } } sourceSets { commonMain.dependencies { implementation(compose.runtime) implementation(compose.foundation) implementation(compose.material3) implementation(compose.ui) implementation(libs.kotlinx.coroutines.core) } androidMain.dependencies { implementation(libs.androidx.activity.compose) implementation(libs.compose.ui.tooling.preview) } } } android { namespace = "com.example.app" compileSdk = 34 defaultConfig { applicationId = "com.example.app" minSdk = 24 targetSdk = 34 versionCode = 1 versionName = "1.0" } buildFeatures { compose = true } compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } } dependencies { debugImplementation(libs.compose.ui.tooling) } ``` --- ## New Structure (AGP 9.x) ``` shared/ build.gradle.kts # kotlin.multiplatform + com.android.kotlin.multiplatform.library src/ commonMain/kotlin/ # All shared KMP code androidMain/kotlin/ # Android-specific implementations (expect/actual) androidMain/res/ # Shared Android resources (if any) iosMain/kotlin/ # iOS-specific code androidApp/ build.gradle.kts # com.android.application ONLY (no kotlin.multiplatform) src/ main/kotlin/ # MainActivity, Application class main/res/ # App-level resources (launcher icons, themes, etc.) main/AndroidManifest.xml # Full manifest with and iosApp/ # Unchanged ``` --- ## androidApp/build.gradle.kts **Important:** In AGP 9.0, the `com.android.application` plugin has Kotlin support built in. Do NOT apply `org.jetbrains.kotlin.android` separately -- it will conflict. ```kotlin plugins { alias(libs.plugins.androidApplication) alias(libs.plugins.composeCompiler) } android { namespace = "com.example.app" compileSdk = 35 defaultConfig { applicationId = "com.example.app" minSdk = 24 targetSdk = 35 versionCode = 1 versionName = "1.0" } buildFeatures { compose = true } compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } kotlin { compilerOptions { jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11) } } buildTypes { release { isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } } } dependencies { implementation(project(":shared")) implementation(libs.androidx.activity.compose) implementation(libs.compose.ui.tooling.preview) debugImplementation(libs.compose.ui.tooling) } ``` ### Key Points for androidApp - **No `kotlin.multiplatform` plugin.** This is a pure Android application module. - **No `kotlin-android` plugin.** AGP 9.0's `com.android.application` plugin bundles Kotlin compilation. Applying `org.jetbrains.kotlin.android` will cause a conflict error. - **`buildTypes` and `productFlavors` work here.** The application plugin still supports full variant configuration. - **Compose compiler plugin** is applied separately (`composeCompiler`), or it can come from KGP 2.0+ if you use the compose compiler Gradle plugin. - **Depends on `:shared`** to access all shared KMP code. --- ## shared/build.gradle.kts ```kotlin plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.androidKmpLibrary) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.composeCompiler) } kotlin { android { namespace = "com.example.shared" compileSdk = 35 minSdk = 24 compilerOptions { jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11) } androidResources { enable = true } } iosX64() iosArm64() iosSimulatorArm64() listOf(iosX64(), iosArm64(), iosSimulatorArm64()).forEach { it.binaries.framework { baseName = "Shared" isStatic = true } } sourceSets { commonMain.dependencies { implementation(compose.runtime) implementation(compose.foundation) implementation(compose.material3) implementation(compose.ui) implementation(libs.kotlinx.coroutines.core) } androidMain.dependencies { // Android-specific shared dependencies only } } } ``` ### Key Points for shared - **Plugin is `com.android.kotlin.multiplatform.library`**, not `com.android.library`. - **Namespace must differ from androidApp.** Use `com.example.shared` vs `com.example.app`. - **No `applicationId`, `versionCode`, `versionName`, `targetSdk`.** These are application-only concepts. - **No `buildTypes` or `productFlavors`.** The KMP library plugin produces a single variant. - **Framework exports** (`binaries.framework`) stay here since iOS depends on the shared module. - **`androidTarget {}`** is replaced with **`android {}`**. --- ## settings.gradle.kts Changes ### Before ```kotlin rootProject.name = "MyProject" include(":composeApp") include(":iosApp") // if present as a Gradle module ``` ### After ```kotlin rootProject.name = "MyProject" include(":shared") include(":androidApp") include(":iosApp") ``` --- ## Root build.gradle.kts Changes ### With Version Catalog **Before:** ```kotlin plugins { alias(libs.plugins.androidApplication) apply false alias(libs.plugins.androidLibrary) apply false alias(libs.plugins.kotlinMultiplatform) apply false alias(libs.plugins.composeMultiplatform) apply false alias(libs.plugins.composeCompiler) apply false } ``` **After:** ```kotlin plugins { alias(libs.plugins.androidApplication) apply false alias(libs.plugins.androidKmpLibrary) apply false alias(libs.plugins.kotlinMultiplatform) apply false alias(libs.plugins.composeMultiplatform) apply false alias(libs.plugins.composeCompiler) apply false } ``` ### Without Version Catalog **Before:** ```kotlin plugins { id("com.android.application") version "8.7.3" apply false id("com.android.library") version "8.7.3" apply false id("org.jetbrains.kotlin.multiplatform") version "2.1.0" apply false id("org.jetbrains.compose") version "1.7.0" apply false id("org.jetbrains.kotlin.plugin.compose") version "2.1.0" apply false } ``` **After:** ```kotlin plugins { id("com.android.application") version "9.0.1" apply false id("com.android.kotlin.multiplatform.library") version "9.0.1" apply false id("org.jetbrains.kotlin.multiplatform") version "2.3.20" apply false id("org.jetbrains.compose") version "1.10.3" apply false id("org.jetbrains.kotlin.plugin.compose") version "2.3.20" apply false } ``` Note: `com.android.library` is replaced with `com.android.kotlin.multiplatform.library`. If you still have pure Android library modules (non-KMP), you can keep `com.android.library` as well. --- ## What to Move to androidApp These items are Android application concerns and must move out of the shared KMP module: ### 1. MainActivity (and any other Activities) ``` composeApp/src/androidMain/kotlin/com/example/app/MainActivity.kt --> androidApp/src/main/kotlin/com/example/app/MainActivity.kt ``` Update `MainActivity` to call into shared code: ```kotlin // androidApp/src/main/kotlin/com/example/app/MainActivity.kt package com.example.app import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import com.example.shared.App // Import from shared module class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { App() // Shared composable } } } ``` ### 2. AndroidManifest.xml The full application manifest with ``, ``, `` moves to androidApp: ``` composeApp/src/androidMain/AndroidManifest.xml --> androidApp/src/main/AndroidManifest.xml ``` **Important:** After moving the manifest, verify the `android:name` attribute on `` points to the correct Activity class in its new location. If the old manifest relied on a default or short class name, you may need to use the fully qualified name: ```xml ``` The shared module may still have a minimal manifest (auto-generated or containing just `` with no ``). ### 3. Application Class (if any) ``` composeApp/src/androidMain/kotlin/.../MyApplication.kt --> androidApp/src/main/kotlin/.../MyApplication.kt ``` ### 4. App-Level Resources - Launcher icons (`mipmap-*`) - App theme definitions that reference `applicationId` - Splash screen resources - Navigation graphs (if not shared) ``` composeApp/src/androidMain/res/mipmap-*/ composeApp/src/androidMain/res/values/themes.xml (app-level theme) --> androidApp/src/main/res/ ``` ### 5. ProGuard Rules ``` composeApp/proguard-rules.pro --> androidApp/proguard-rules.pro ``` --- ## What Stays in shared - All `commonMain` code (ViewModels, repositories, models, shared composables) - All `expect`/`actual` declarations - All `iosMain`, `desktopMain`, `wasmJsMain` code - Framework export configuration (`binaries.framework`) - Shared Android resources (strings, drawables used by shared composables) - Shared Android-specific implementations (`actual` functions) --- ## Namespace Requirements The `namespace` for each module must be unique: ```kotlin // shared/build.gradle.kts kotlin { android { namespace = "com.example.shared" } } // androidApp/build.gradle.kts android { namespace = "com.example.app" } ``` If they collide, you will get duplicate R class errors at compile time. The `applicationId` (in androidApp only) can be different from both namespaces. --- ## Run Configuration Updates ### Android Studio After the split, the run configuration for the Android app must point to `:androidApp` instead of `:composeApp`: 1. Edit Run Configurations 2. Change Module to `androidApp` 3. Ensure the launch activity is `com.example.app.MainActivity` ### Xcode (iOS) If the shared module was renamed from `composeApp` to `shared`: 1. **Update `baseName` in `shared/build.gradle.kts`** to match the new module name: ```kotlin listOf(iosX64(), iosArm64(), iosSimulatorArm64()).forEach { it.binaries.framework { baseName = "Shared" // was "ComposeApp" isStatic = true } } ``` 2. **Update the Run Script build phase** in `project.pbxproj` (or via Xcode > Build Phases > Run Script) to reference the new module: ```bash # Old cd "$SRCROOT/.." ./gradlew :composeApp:embedAndSignAppleFrameworkForXcode # New cd "$SRCROOT/.." ./gradlew :shared:embedAndSignAppleFrameworkForXcode ``` 3. **Update Swift imports** — in all `.swift` files, change the framework import to match `baseName`: ```swift // Old import ComposeApp // New import Shared ``` 4. **Update the app struct name** if it was tied to the old module name. The `@main` struct name in your SwiftUI app entry point is independent of the framework name, but if it referenced the old name, rename it: ```swift // Example: rename if it was called ComposeAppApp or similar @main struct MyApp: App { var body: some Scene { WindowGroup { ContentView() } } } ``` 5. **Update framework search paths** in Xcode Build Settings if they reference the old module directory path. --- ## Quick Checklist - [ ] Create `androidApp/` directory with `build.gradle.kts` - [ ] Move `MainActivity` and `Application` class to `androidApp` - [ ] Move `AndroidManifest.xml` (full manifest) to `androidApp` - [ ] Move app-level resources (launcher icons, app theme) to `androidApp` - [ ] Move ProGuard rules to `androidApp` - [ ] Convert `composeApp` to `shared` with KMP library plugin - [ ] Remove application-only config (`applicationId`, `versionCode`, `buildTypes`) from shared - [ ] Add `implementation(project(":shared"))` to androidApp dependencies - [ ] Update `settings.gradle.kts` includes - [ ] Update root `build.gradle.kts` plugin declarations - [ ] Ensure namespaces are different between modules - [ ] Update Android Studio run configuration - [ ] Update Xcode project if iOS target exists - [ ] Run `./gradlew :androidApp:assembleDebug` and `./gradlew :shared:assemble` to verify