refactor(page): 删除旧版页面文件

This commit is contained in:
2026-08-16 05:31:45 +08:00
parent ca76678afc
commit 1396487af9
88 changed files with 12633 additions and 1081 deletions

View File

@@ -0,0 +1,286 @@
# DSL Reference: AGP 8.x to AGP 9.x KMP Library Migration
Side-by-side mapping of every DSL element from the old `com.android.library` configuration to the new `com.android.kotlin.multiplatform.library` configuration.
---
## Plugin IDs
| Old (AGP 8.x) | New (AGP 9.x) |
|--------------------------------------|-----------------------------------------------------------------------------------------------------|
| `com.android.library` | `com.android.kotlin.multiplatform.library` |
| `com.android.application` | `com.android.application` (unchanged, but cannot combine with KMP) |
| `org.jetbrains.kotlin.android` | Built into `com.android.application` and `com.android.library` in AGP 9.0 (do not apply separately) |
| `org.jetbrains.kotlin.kapt` | `com.android.legacy-kapt` (same version as AGP) or migrate to KSP |
| `org.jetbrains.kotlin.multiplatform` | `org.jetbrains.kotlin.multiplatform` (unchanged) |
---
## Top-Level Block Migration
| Old | New |
|-------------------------------------------|-------------------------------------|
| `android { ... }` | `kotlin { android { ... } }` |
| `androidTarget { ... }` (in kotlin block) | `android { ... }` (in kotlin block) |
---
## android {} Block Fields
### Namespace and SDK Versions
| Old (android {}) | New (kotlin { android {} }) |
|------------------------------------|-------------------------------------------------------------------------|
| `namespace = "..."` | `namespace = "..."` |
| `compileSdk = 35` | `compileSdk = 35` (same value, just moved into `kotlin { android {} }`) |
| `defaultConfig { minSdk = 24 }` | `minSdk = 24` |
| `defaultConfig { targetSdk = 34 }` | N/A (application-only, not in library) |
### defaultConfig Elements
| Old (android { defaultConfig {} }) | New (kotlin { android {} }) |
|--------------------------------------------|-------------------------------------------|
| `minSdk = 24` | `minSdk = 24` (direct property) |
| `testInstrumentationRunner = "..."` | Set in `withDeviceTest { }` configuration |
| `consumerProguardFiles("...")` | `consumerProguardFiles.add(file("..."))` |
| `multiDexEnabled = true` | N/A (handled automatically) |
| `vectorDrawables.useSupportLibrary = true` | N/A |
| `buildConfigField(...)` | Removed (see KNOWN-ISSUES.md) |
| `manifestPlaceholders[...]` | N/A (use merged manifest in app module) |
---
## Compile Options and Compiler Options
| Old | New |
|-------------------------------------------------------------------------------|------------------------------------------------------------------------------|
| `android { compileOptions { sourceCompatibility = JavaVersion.VERSION_11 } }` | `kotlin { android { compilerOptions { jvmTarget.set(JvmTarget.JVM_11) } } }` |
| `android { compileOptions { targetCompatibility = JavaVersion.VERSION_11 } }` | `kotlin { android { compilerOptions { jvmTarget.set(JvmTarget.JVM_11) } } }` |
| `kotlinOptions { jvmTarget = "11" }` | `compilerOptions { jvmTarget.set(JvmTarget.JVM_11) }` |
| `kotlinOptions { freeCompilerArgs += listOf("-Xopt-in=...") }` | `compilerOptions { optIn.add("...") }` |
| `kotlinOptions { languageVersion = "1.9" }` | `compilerOptions { languageVersion.set(KotlinVersion.KOTLIN_2_0) }` |
Full JvmTarget import:
```kotlin
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
```
---
## Build Features
| Old (android { buildFeatures {} }) | New (kotlin { android {} }) |
|-----------------------------------------|------------------------------------------------------------------------------|
| `buildFeatures { compose = true }` | Applied via compose compiler plugin (no explicit flag needed in KMP library) |
| `buildFeatures { buildConfig = true }` | Removed in KMP library (see KNOWN-ISSUES.md) |
| `buildFeatures { viewBinding = true }` | Not supported in KMP library |
| `buildFeatures { dataBinding = true }` | Not supported in KMP library |
| `buildFeatures { aidl = true }` | Not supported in KMP library |
| `buildFeatures { renderScript = true }` | Not supported |
| `buildFeatures { resValues = true }` | Not supported in KMP library |
---
## Android Resources
| Old | New |
|---------------------------------------------|-------------------------------------------------------------|
| Resources processed by default | Must explicitly enable |
| `android { ... }` (resources auto-included) | `kotlin { android { androidResources { enable = true } } }` |
---
## Test Options
| Old | New |
|----------------------------------------------------------------------|--------------------------------------------------------------------------------|
| `android { testOptions { unitTests.isReturnDefaultValues = true } }` | `kotlin { android { withHostTest { } } }` |
| `android { testOptions { animationsDisabled = true } }` | `kotlin { android { withDeviceTest { } } }` |
| Source set: `androidUnitTest` | Source set: `androidHostTest` (alias: `androidUnitTest` still works) |
| Source set: `androidInstrumentedTest` | Source set: `androidDeviceTest` (alias: `androidInstrumentedTest` still works) |
| `testImplementation(...)` | `getByName("androidHostTest").dependencies { implementation(...) }` |
| `androidTestImplementation(...)` | `getByName("androidDeviceTest").dependencies { implementation(...) }` |
| Source dir: `src/test/` | Source dir: `src/androidHostTest/kotlin/` |
| Source dir: `src/androidTest/` | Source dir: `src/androidDeviceTest/kotlin/` |
### Test Configuration Details
```kotlin
// Old
android {
testOptions {
unitTests {
isReturnDefaultValues = true
isIncludeAndroidResources = true
}
}
}
// New
kotlin {
android {
withHostTest {
// Host test specific configuration
// returnDefaultValues and includeAndroidResources
// are configured via gradle.properties or test runner
}
withDeviceTest {
// Device test specific configuration
instrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
}
}
```
---
## Lint Configuration
| Old | New |
|-------------------------------------------------------------|------------------------------------------------------------------------|
| `android { lint { abortOnError = false } }` | `kotlin { android { lint { abortOnError = false } } }` |
| `android { lint { checkReleaseBuilds = true } }` | `kotlin { android { lint { checkReleaseBuilds = true } } }` |
| `android { lint { disable += "SomeCheck" } }` | `kotlin { android { lint { disable += "SomeCheck" } } }` |
| `android { lint { baseline = file("lint-baseline.xml") } }` | `kotlin { android { lint { baseline = file("lint-baseline.xml") } } }` |
The lint DSL is largely unchanged, it just moves inside `kotlin { android {} }`.
**Note:** `useK2Uast` is deprecated. Remove it if present.
---
## Packaging / Resources Excludes
| Old | New |
|-----------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|
| `android { packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } } }` | `kotlin { android { packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } } } }` |
The DSL is the same, just nested under `kotlin { android {} }`.
**Syntax note for AGP 9.0:**
```kotlin
// Old syntax (still works but deprecated)
resources.excludes += "/META-INF/{AL2.0,LGPL2.1}"
// Preferred AGP 9.0 syntax
resources {
excludes.add("/META-INF/AL2.0")
excludes.add("/META-INF/LGPL2.1")
}
```
---
## Dependencies Configurations
| Old Configuration | New Configuration | Notes |
|----------------------------------|-----------------------------------------------------------------------|-----------------------------|
| `implementation(...)` | `androidMain.dependencies { implementation(...) }` | Move to source set |
| `api(...)` | `androidMain.dependencies { api(...) }` | Move to source set |
| `compileOnly(...)` | `androidMain.dependencies { compileOnly(...) }` | Move to source set |
| `debugImplementation(...)` | `"androidRuntimeClasspath"(...)` | No variant-specific configs |
| `releaseImplementation(...)` | `androidMain.dependencies { implementation(...) }` | Single variant |
| `testImplementation(...)` | `getByName("androidHostTest").dependencies { implementation(...) }` | |
| `androidTestImplementation(...)` | `getByName("androidDeviceTest").dependencies { implementation(...) }` | |
| `ksp(...)` | `add("ksp", ...)` or KSP Gradle plugin DSL | Check KSP compatibility |
| `kapt(...)` | Migrate to KSP; kapt not supported | |
---
## Dependency Resolution
Because the new KMP Android library plugin is strictly single-variant, you can no longer define fallback logic inside `buildTypes` or `defaultConfig`.
| Old | New | Notes |
|---|---|---|
| `android { defaultConfig { missingDimensionStrategy("tier", "free") } }` | `kotlin { android { localDependencySelection { productFlavorDimension("tier") { selectFrom.set(listOf("free")) } } } }` | Configure dependency flavor fallbacks |
| `android { buildTypes { getByName("debug") { matchingFallbacks.add("release") } } }` | `kotlin { android { localDependencySelection { selectBuildTypeFrom.set(listOf("debug", "release")) } } }` | Configure dependency build type mapping |
---
## Build Types and Product Flavors
**Removed in KMP library plugin.** The `com.android.kotlin.multiplatform.library` plugin produces a single build variant.
| Old | New | Notes |
|---------------------------------------------------|---------|-----------------------------------------------------|
| `buildTypes { debug { ... } }` | Removed | Single variant only |
| `buildTypes { release { minifyEnabled = true } }` | Removed | Minification is app-module concern |
| `productFlavors { ... }` | Removed | Use Gradle properties or expect/actual for variants |
| `flavorDimensions(...)` | Removed | |
### Workarounds for Variant-Dependent Logic
1. **Compile-time constants:** Use `expect`/`actual` or dependency injection instead of `BuildConfig`.
2. **Environment-specific behavior:** Use Gradle properties or runtime configuration.
3. **Different dependencies per build type:** Not possible in KMP library. Move to app module.
4. **Minification/ProGuard:** Only relevant in the application module.
---
## androidComponents Block
| Old | New |
|------------------------------------------------|--------------------------------------------------|
| `androidComponents { onVariants { ... } }` | Limited support; most variant API is unavailable |
| `androidComponents { beforeVariants { ... } }` | Not available in KMP library |
| `androidComponents { finalizeDsl { ... } }` | Not available in KMP library |
The `androidComponents` extension is significantly reduced in scope for KMP libraries because there is only a single variant. Most customization that relied on variant-aware APIs must be reworked.
**Note:** `android.enableLegacyVariantApi` is **removed** in AGP 9.0 and will cause an error if set. Code depending on legacy variant APIs must be migrated to `androidComponents` APIs.
---
## Java Source Compilation
| Old | New |
|---------------------------------------------------------|-------------------------------------|
| Java sources in `src/main/java/` compiled automatically | Must call `withJava()` |
| `android { compileOptions { ... } }` | `kotlin { android { withJava() } }` |
```kotlin
kotlin {
android {
withJava() // Required to compile .java files in androidMain
}
}
```
---
## Quick Reference: Minimal Migration Template
```kotlin
// OLD
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidLibrary)
}
kotlin {
androidTarget { compilations.all { kotlinOptions { jvmTarget = "11" } } }
}
android {
namespace = "com.example.lib"
compileSdk = 35
defaultConfig { minSdk = 24 }
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
// NEW
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidKmpLibrary)
}
kotlin {
android {
namespace = "com.example.lib"
compileSdk = 35
minSdk = 24
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
}
}
}
```

View File

@@ -0,0 +1,546 @@
# Known Issues: KMP AGP 9.0 Library Migration
Comprehensive list of gotchas, limitations, and workarounds when migrating to `com.android.kotlin.multiplatform.library`. Based on official Android documentation and community experience.
---
## 1. BuildConfig Removed in Libraries
**Problem:** The `BuildConfig` class is not generated for KMP library modules. Code referencing `BuildConfig.DEBUG`, `BuildConfig.VERSION_NAME`, or custom `buildConfigField` entries will fail to compile.
**Impact:** High. Many libraries use `BuildConfig.DEBUG` for logging gates and `buildConfigField` for compile-time constants.
**Workaround -- AppConfiguration DI Pattern:**
```kotlin
// In commonMain
expect class AppConfiguration {
val isDebug: Boolean
val versionName: String
val apiBaseUrl: String
}
// In androidMain
actual class AppConfiguration(private val context: Context) {
actual val isDebug: Boolean = (context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
actual val versionName: String = context.packageManager
.getPackageInfo(context.packageName, 0).versionName ?: "unknown"
actual val apiBaseUrl: String = if (isDebug) "https://dev.api.example.com" else "https://api.example.com"
}
// In iosMain
actual class AppConfiguration {
actual val isDebug: Boolean = Platform.isDebugBinary
actual val versionName: String = NSBundle.mainBundle.objectForInfoDictionaryKey("CFBundleShortVersionString") as? String ?: "unknown"
actual val apiBaseUrl: String = if (isDebug) "https://dev.api.example.com" else "https://api.example.com"
}
```
Inject `AppConfiguration` via your DI framework (Koin, kotlin-inject, manual DI).
**Alternative A — BuildKonfig plugin** ([github.com/yshrsmz/BuildKonfig](https://github.com/yshrsmz/BuildKonfig)):
Generates `expect`/`actual` BuildConfig objects across all KMP targets. Supports typed fields
(String, Int, Long, Float, Boolean), target-specific overrides, and a flavor system via Gradle properties.
```kotlin
// build.gradle.kts
plugins {
id("com.codingfeline.buildkonfig")
}
buildkonfig {
packageName = "com.example.shared"
defaultConfigs {
buildConfigField(STRING, "API_BASE_URL", "https://api.example.com")
buildConfigField(BOOLEAN, "IS_DEBUG", "false")
buildConfigField(STRING, "VERSION_NAME", "1.0.0")
}
// Optional: target-specific overrides
targetConfigs {
create("android") {
buildConfigField(STRING, "PLATFORM", "android")
}
create("ios") {
buildConfigField(STRING, "PLATFORM", "ios")
}
}
}
```
Use flavors for debug/release by setting `buildkonfig.flavor=dev` in `gradle.properties`
or passing `-Pbuildkonfig.flavor=release` on CLI:
```kotlin
defaultConfigs("dev") {
buildConfigField(STRING, "API_BASE_URL", "https://dev.api.example.com")
buildConfigField(BOOLEAN, "IS_DEBUG", "true")
}
defaultConfigs("release") {
buildConfigField(STRING, "API_BASE_URL", "https://api.example.com")
buildConfigField(BOOLEAN, "IS_DEBUG", "false")
}
```
**Alternative B — gradle-buildconfig-plugin** ([github.com/gmazzo/gradle-buildconfig-plugin](https://github.com/gmazzo/gradle-buildconfig-plugin)):
More general-purpose; supports Java, Kotlin, Groovy, and KMP. Richer type support (arrays, maps,
Files, URIs). Uses `expect`/`actual` for KMP via explicit `expect()` calls.
```kotlin
// build.gradle.kts
plugins {
id("com.github.gmazzo.buildconfig")
}
buildConfig {
packageName("com.example.shared")
buildConfigField("APP_NAME", project.name)
buildConfigField("VERSION", "1.0.0")
buildConfigField("IS_DEBUG", false)
// Platform-specific fields using expect/actual
buildConfigField("PLATFORM", expect<String>())
}
// In source set configurations:
sourceSets.named("androidMain") {
buildConfigField("PLATFORM", "android")
}
sourceSets.named("iosMain") {
buildConfigField("PLATFORM", "ios")
}
```
**Important limitation:** Neither plugin replaces Android build variants fully. They provide
compile-time constants only. Build type-specific dependencies, resources, source sets, signing
configs, and minification settings must be handled in the application module (which still supports
variants) or via runtime configuration.
---
## 2. NDK / JNI Unsupported
**Problem:** The KMP library plugin does not support `externalNativeBuild`, `ndkVersion`, or JNI source compilation. Modules that use C/C++ native code via NDK cannot be migrated directly.
**Impact:** Medium. Affects modules with native image processing, crypto, or media libraries.
**Workaround -- Proxy Interface Pattern:**
Keep the JNI module as a classic `com.android.library` module and have the KMP module depend on it:
```
jni-bridge/ # com.android.library (AGP 8.x compatible in AGP 9.0)
build.gradle.kts
src/main/jni/ # C/C++ sources
src/main/kotlin/ # JNI bindings
shared/ # com.android.kotlin.multiplatform.library
build.gradle.kts
```
```kotlin
// shared/build.gradle.kts
kotlin {
sourceSets {
androidMain.dependencies {
implementation(project(":jni-bridge"))
}
}
}
```
Define an interface in `commonMain` and implement it in `androidMain` by delegating to the JNI bridge.
---
## 3. No Build Variants
**Problem:** The KMP library plugin produces a single build variant. There are no `debug`/`release` build types and no product flavors. Code that depends on variant-specific behavior, resources, or dependencies must be restructured.
**Impact:** High. Affects projects using flavor-specific dependencies, resources, or source sets.
**Workaround -- Single Variant Architecture:**
- Move all variant-dependent logic to the application module (which still supports variants).
- Use runtime configuration instead of compile-time variants.
- Use `expect`/`actual` with different actual implementations selected by DI based on runtime config.
- For library-specific debug/release behavior, use the `AppConfiguration` pattern from issue 1.
- For compile-time constants that vary by build flavor, use **BuildKonfig** or **gradle-buildconfig-plugin** (see issue 1 alternatives). These provide a flavor-like system for KMP but do NOT replace variant-specific dependencies, resources, signing, or minification.
---
## 4. Compose Resources Require Explicit Enable
**Problem:** Android resources (`res/` directory) are not processed by default with the KMP library plugin. If you forget to enable them, resource references (`R.string.*`, `R.drawable.*`) will fail to resolve. This is tracked as CMP-9547.
**Impact:** High. Silent failure -- resources are simply ignored without an error until you try to reference them.
**Fix:**
```kotlin
kotlin {
android {
androidResources { enable = true }
}
}
```
**Note:** This is separate from Compose Multiplatform resources (`composeResources/`), which are handled by the compose resources plugin and do not need this flag.
---
## 5. Consumer ProGuard Rules Silently Dropped
**Problem:** If you had `consumerProguardFiles` in the old `android { defaultConfig {} }` block and did not migrate it to the new DSL location, the rules are silently ignored. No warning is emitted.
**Impact:** Medium. Can cause runtime crashes in release builds of consuming applications.
**Fix:**
```kotlin
// Old (silently ignored)
android {
defaultConfig {
consumerProguardFiles("consumer-rules.pro")
}
}
// New
kotlin {
android {
consumerProguardFiles.add(file("consumer-rules.pro"))
}
}
```
---
## 6. Convention Plugin Refactoring Needed
**Problem:** Build-logic convention plugins that apply `com.android.library` and configure the `LibraryExtension` must be rewritten to use the KMP library plugin and `KotlinMultiplatformExtension`.
**Impact:** Medium to High for projects with extensive build-logic modules.
**Key Changes:**
```kotlin
// Old
import com.android.build.gradle.LibraryExtension
class MyConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
target.pluginManager.apply("com.android.library")
target.extensions.configure<LibraryExtension> {
compileSdk = 34
defaultConfig.minSdk = 24
}
}
}
// New
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
class MyConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
target.pluginManager.apply("org.jetbrains.kotlin.multiplatform")
target.pluginManager.apply("com.android.kotlin.multiplatform.library")
target.extensions.configure<KotlinMultiplatformExtension> {
android {
compileSdk = 35
minSdk = 24
}
}
}
}
```
---
## 7. Renamed test source sets
**Problem:** The source set `androidUnitTest` is renamed to `androidHostTest`. The source set `androidInstrumentedTest` is renamed to `androidDeviceTest`. The old names still work as aliases but are deprecated.
**Impact:** Low. Aliases provide backward compatibility, but you should rename for clarity.
**Action Items:**
- Rename `src/androidUnitTest/` to `src/androidHostTest/`
- Rename `src/androidInstrumentedTest/` to `src/androidDeviceTest/`
- Update `sourceSets` references in `build.gradle.kts`
- Update CI scripts that reference the old directory names
---
## 8. Lint useK2Uast Deprecated
**Problem:** The `lint { useK2Uast = true }` option is deprecated. With KGP 2.0+ and AGP 9.0, K2 UAST is the default and only implementation.
**Impact:** Low. Build warning only.
**Fix:** Remove the line:
```kotlin
// Remove this
lint {
useK2Uast = true // DELETE
}
```
---
## 9. Packaging Exclusions Syntax Change
**Problem:** The packaging exclusions DSL has a subtle syntax difference. The old brace-expansion syntax may not work correctly.
**Impact:** Low. Build may fail or produce unexpected results.
**Fix:**
```kotlin
// Old (may not work correctly in AGP 9.0)
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
// New (explicit entries)
packaging {
resources {
excludes.add("/META-INF/AL2.0")
excludes.add("/META-INF/LGPL2.1")
}
}
```
---
## 10. Static BuildConfig.DEBUG for Tree-Shaking No Longer Available
**Problem:** In classic Android libraries, `BuildConfig.DEBUG` was a `static final boolean` that the compiler could use for dead-code elimination (tree-shaking). Without BuildConfig in KMP libraries, this optimization path is lost.
**Impact:** Low to Medium. Debug-only code paths may be included in release builds.
**Workaround:**
Use R8 rules in the application module to remove debug code:
```proguard
# In the app module's proguard-rules.pro
-assumenosideeffects class com.example.shared.AppConfiguration {
boolean isDebug() return false;
}
```
Or use compile-time constants from the application module passed via DI.
---
## 11. android.builtInKotlin=false (Temporary Opt-Out)
**Problem:** AGP 9.0 bundles Kotlin compilation for `com.android.application` modules, meaning you should NOT apply the `kotlin-android` plugin separately. If you have tooling or plugins that conflict with the built-in Kotlin support, you can temporarily disable it.
**Temporary Workaround:**
```properties
# gradle.properties
android.builtInKotlin=false
```
**Warning:** This flag is temporary and will be removed in a future AGP version. Use it only to unblock migration while you fix plugin conflicts.
**When you need this:**
- You have custom Kotlin compiler plugin configurations that conflict with AGP's built-in setup
- A third-party Gradle plugin explicitly applies `kotlin-android` and conflicts
---
## 12. IDE Support Matrix
| IDE | KMP Library Plugin Support | Notes |
|---|---|-----------------------------------------------------------|
| Android Studio Otter 3 (2025.2.3) | Full | First version with full AGP 9.0 support |
| Android Studio Narwhal (2025.1) | Partial | Can build but limited code insight for new DSL |
| Android Studio Meerkat (2024.3) | None | Does not recognize the KMP library plugin |
| IntelliJ IDEA | Not supported | Does not support AGP 9.0 as of 2025.3, use Android Studio |
### Recommended IDE Setup
- **Use Android Studio Otter 3 or later.** This is currently the only IDE with full AGP 9.0 + KMP library plugin support.
- **IntelliJ IDEA does not support AGP 9.0** — even with the Android plugin installed, project sync and build integration will not work correctly. If the project uses IntelliJ IDEA, recommend switching to Android Studio for Android/KMP development.
- Ensure the Kotlin plugin in your IDE matches the KGP version in your project.
---
## 13. kapt Incompatible with Built-in Kotlin
**Problem:** The `org.jetbrains.kotlin.kapt` plugin is incompatible with AGP 9.0's built-in Kotlin support. Applying both causes a build failure.
**Impact:** High. Many projects still use kapt for annotation processing
**Workaround:**
**Preferred:** Migrate to KSP (Kotlin Symbol Processing). Most annotation processors now support KSP.
**Fallback:** Replace with `com.android.legacy-kapt` (versioned with AGP):
```toml
# gradle/libs.versions.toml
[plugins]
legacy-kapt = { id = "com.android.legacy-kapt", version.ref = "agp" }
```
```kotlin
// Module build.gradle.kts
plugins {
// REMOVE: alias(libs.plugins.kotlin.kapt)
alias(libs.plugins.legacy.kapt)
}
```
---
## 14. New DSL Interfaces (BaseExtension Removed)
**Problem:** AGP 9.0 exclusively uses new public DSL interfaces. The old `BaseExtension`, `AppExtension`, `LibraryExtension` types from `com.android.build.gradle` are removed. Build logic or convention plugins casting to these types will fail with `ClassCastException`.
**Impact:** High for projects with custom build logic or convention plugins.
**Error message:**
```
java.lang.ClassCastException: class com.android.build.gradle.internal.dsl.ApplicationExtensionImpl$AgpDecorated_Decorated
cannot be cast to class com.android.build.gradle.BaseExtension
```
**Fix:**
```kotlin
// Old
import com.android.build.gradle.BaseExtension
val ext = extensions.getByType(BaseExtension::class)
// New
import com.android.build.api.dsl.CommonExtension
val ext = extensions.getByType(CommonExtension::class)
```
**Temporary opt-out:** `android.newDsl=false` in `gradle.properties` (removed in AGP 10.0).
---
## 15. Deprecated Variant APIs Removed
**Problem:** The following APIs are removed in AGP 9.0: `applicationVariants`, `libraryVariants`, `testVariants`, `unitTestVariants`, `variantFilter`. Build scripts or plugins using these will fail.
**Impact:** Medium-High. Affects custom build logic and many third-party plugins.
**Fix:**
```kotlin
// Old
android {
applicationVariants.all { variant ->
variant.signingConfig.enableV1Signing = false
}
}
// New
androidComponents {
onVariants { variant ->
variant.signingConfig.enableV1Signing.set(false)
}
}
```
Replace `variantFilter` with `androidComponents.beforeVariants()`.
---
## 16. R8 and ProGuard Rule Changes
**Problem:** AGP 9.0 changes several R8 defaults:
- `android.r8.strictFullModeForKeepRules=true` — keep rules no longer implicitly keep default constructors
- `android.r8.proguardAndroidTxt.disallowed=true` — only `proguard-android-optimize.txt` is supported
- `android.r8.globalOptionsInConsumerRules.disallowed=true` — library consumer rules cannot contain global options (like `-dontobfuscate`)
- Keep rules no longer propagate to synthesized companion methods
**Impact:** Medium. Release builds may crash or behave differently without rule updates.
**Fix:**
- Review all ProGuard/R8 keep rules; add explicit rules for default constructors if needed
- Switch to `proguard-android-optimize.txt` in `getDefaultProguardFile()`
- Remove global options (`-dontobfuscate`, `-dontoptimize`) from library consumer rules
- New option: `-processkotlinnullchecks keep|remove_message|remove` to control Kotlin null checks
---
## 17. Removed Features
**Problem:** Several features are removed in AGP 9.0 with no replacement:
- **Embedded Wear OS app support** — `wearApp` configurations removed
- **Density split APK** — use app bundles instead
- **`androidDependencies` and `sourceSets` report tasks** — removed
- **`dexOptions` DSL** — removed (d8 handles this automatically)
- **RenderScript** — disabled by default, enable per-module if needed: `buildFeatures { renderScript = true }`
- **AIDL** — disabled by default, enable per-module if needed: `buildFeatures { aidl = true }`
**Impact:** Low-Medium. Only affects projects using these specific features.
---
## 18. R Class Non-Final in Application Modules
**Problem:** AGP 9.0 makes R class fields compile-time non-final in application modules (`android.enableAppCompileTimeRClass=true` is now default). Code using `switch` statements on R class fields (like `R.id.some_view`) will fail to compile because `switch` requires compile-time constants.
**Impact:** Medium. Common in older Java codebases using `switch(view.getId())`.
**Fix:** Refactor `switch` statements to `if/else`:
```java
// Old (fails with AGP 9.0)
switch (view.getId()) {
case R.id.button1: // ...
case R.id.button2: // ...
}
// New
int id = view.getId();
if (id == R.id.button1) { /* ... */ }
else if (id == R.id.button2) { /* ... */ }
```
---
## 19. targetSdk Defaults to compileSdk
**Problem:** AGP 9.0 changes `targetSdk` to default to `compileSdk` when not explicitly set (previously defaulted to `minSdk`). This can silently change app behavior if `targetSdk` was intentionally unset.
**Impact:** Medium. May trigger new runtime behavior changes associated with higher API levels.
**Fix:** Explicitly set `targetSdk` in all application modules:
```kotlin
android {
defaultConfig {
targetSdk = 35 // Set explicitly
}
}
```
---
## 20. Third-Party Plugin Compatibility
**Problem:** Many third-party Gradle plugins are incompatible with AGP 9.0 due to removed variant APIs, new DSL interfaces, or built-in Kotlin conflicts. See the main SKILL.md "Plugin Compatibility" section for the full compatibility table.
**Impact:** High. Can completely block migration.
**Key plugins requiring opt-out flags:**
- detekt < 2.0.0, ktlint, SQLDelight, Paparazzi, protobuf — see SKILL.md for specific flags
---

View File

@@ -0,0 +1,505 @@
# 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 <application> and <activity>
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 `<application>`, `<activity>`, `<intent-filter>` moves to androidApp:
```
composeApp/src/androidMain/AndroidManifest.xml
--> androidApp/src/main/AndroidManifest.xml
```
**Important:** After moving the manifest, verify the `android:name` attribute on `<activity>` 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
<activity
android:name="com.example.app.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
```
The shared module may still have a minimal manifest (auto-generated or containing just `<manifest>` with no `<application>`).
### 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

View File

@@ -0,0 +1,478 @@
# Full Restructure: Extracting All Platform Entry Points
This guide covers the complete extraction of platform-specific entry points from a monolithic `composeApp` module into dedicated per-platform application modules. This is the most thorough migration path and results in a clean architecture where `shared` contains only cross-platform code.
---
## Target Architecture
```
shared/ # KMP library (all shared code)
build.gradle.kts # kotlin.multiplatform + com.android.kotlin.multiplatform.library
src/
commonMain/kotlin/ # Shared business logic + UI
androidMain/kotlin/ # Android expect/actual implementations
iosMain/kotlin/ # iOS expect/actual implementations
androidApp/ # Android application entry point
build.gradle.kts # com.android.application
src/main/
desktopApp/ # Desktop (JVM) application entry point
build.gradle.kts # org.jetbrains.compose + application {}
src/main/kotlin/
webApp/ # Wasm/JS web application entry point
build.gradle.kts # kotlin.multiplatform + wasmJs target
src/wasmJsMain/kotlin/
iosApp/ # iOS application (Xcode project, usually already separate)
iosApp.xcodeproj/
```
---
## Desktop Extraction
### Create desktopApp/build.gradle.kts
```kotlin
plugins {
alias(libs.plugins.kotlinJvm)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
}
dependencies {
implementation(project(":shared"))
implementation(compose.desktop.currentOs)
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
}
compose.desktop {
application {
mainClass = "com.example.app.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 = "com.example.app"
packageVersion = "1.0.0"
macOS {
iconFile.set(project.file("icons/icon.icns"))
}
windows {
iconFile.set(project.file("icons/icon.ico"))
}
linux {
iconFile.set(project.file("icons/icon.png"))
}
}
}
}
```
### Move Desktop Entry Point
```
composeApp/src/desktopMain/kotlin/com/example/app/main.kt
--> desktopApp/src/main/kotlin/com/example/app/main.kt
```
Update to call shared code:
```kotlin
// desktopApp/src/main/kotlin/com/example/app/main.kt
package com.example.app
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import com.example.shared.App
fun main() = application {
Window(
onCloseRequest = ::exitApplication,
title = "My App"
) {
App()
}
}
```
### Remove Desktop from shared
In `shared/build.gradle.kts`, remove the `jvm("desktop")` target entirely. The desktop target only needs to exist in `desktopApp`.
**Before (in composeApp):**
```kotlin
kotlin {
jvm("desktop")
// ...
sourceSets {
val desktopMain by getting {
dependencies {
implementation(compose.desktop.currentOs)
}
}
}
}
compose.desktop {
application {
mainClass = "com.example.app.MainKt"
nativeDistributions { ... }
}
}
```
**After (in shared):**
```kotlin
kotlin {
// jvm("desktop") -- REMOVED
// No desktop target in shared module
// No compose.desktop block
}
```
If you have shared JVM code that both Android and Desktop use, you have two options:
1. Keep a `jvm()` target in shared (without the `application {}` block) and use intermediate source sets.
2. Put all shared code in `commonMain` and rely on the JVM dependency from `desktopApp`.
---
## Web/WasmJS Extraction
### Create webApp/build.gradle.kts
```kotlin
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
}
kotlin {
wasmJs {
browser {
commonWebpackConfig {
outputFileName = "app.js"
}
}
binaries.executable()
}
sourceSets {
wasmJsMain.dependencies {
implementation(project(":shared"))
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation(compose.ui)
}
}
}
```
### Move Web Entry Point
```
composeApp/src/wasmJsMain/kotlin/com/example/app/main.kt
--> webApp/src/wasmJsMain/kotlin/com/example/app/main.kt
```
Update to call shared code:
```kotlin
// webApp/src/wasmJsMain/kotlin/com/example/app/main.kt
package com.example.app
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.window.CanvasBasedWindow
import com.example.shared.App
@OptIn(ExperimentalComposeUiApi::class)
fun main() {
CanvasBasedWindow(canvasElementId = "ComposeTarget") {
App()
}
}
```
### Move Web Resources
```
composeApp/src/wasmJsMain/resources/index.html
--> webApp/src/wasmJsMain/resources/index.html
```
Update `index.html` if the output JS filename changed.
### Remove WasmJS from shared
In `shared/build.gradle.kts`, remove the `wasmJs {}` target:
```kotlin
kotlin {
// wasmJs { ... } -- REMOVED
}
```
If you need shared Wasm-compatible code, keep `wasmJs()` in shared as a library target (no `binaries.executable()`, no `browser {}` config).
---
## iOS Handling
iOS is typically already a separate Xcode project. The main considerations during restructure:
### Framework Export Stays in shared
```kotlin
// shared/build.gradle.kts
kotlin {
listOf(iosX64(), iosArm64(), iosSimulatorArm64()).forEach {
it.binaries.framework {
baseName = "Shared" // Update if renamed from "ComposeApp"
isStatic = true
}
}
}
```
### Update Xcode Project
If the module was renamed from `composeApp` to `shared`:
1. **Framework import:** Change `import ComposeApp` to `import Shared` in all `.swift` files (must match `baseName` in the framework config).
2. **Gradle task path:** Update the Run Script build phase in `project.pbxproj` (or via Xcode > Build Phases):
```bash
# In Xcode Build Phases > Run Script
cd "$SRCROOT/.."
./gradlew :shared:embedAndSignAppleFrameworkForXcode
```
3. **App struct name:** If the SwiftUI `@main` struct was named after the old module (e.g., `ComposeAppApp`), rename it to something appropriate for your project.
4. **Framework search paths:** Update Build Settings if they reference the old module directory path.
5. **Cocoapods (if used):** Update the pod spec name:
```kotlin
// shared/build.gradle.kts
kotlin {
cocoapods {
name = "Shared"
summary = "Shared KMP module"
// ...
}
}
```
---
## Module Rename: composeApp to shared
### 1. Rename the Directory
```bash
mv composeApp shared
```
### 2. Update settings.gradle.kts
```kotlin
// Before
include(":composeApp")
// After
include(":shared")
include(":androidApp")
include(":desktopApp")
include(":webApp")
```
### 3. Update Cross-Module Dependencies
Search all `build.gradle.kts` files for references to `:composeApp`:
```kotlin
// Before
implementation(project(":composeApp"))
// After
implementation(project(":shared"))
```
### 4. Update .idea / Workspace Files
If using IntelliJ/Android Studio, the IDE may cache the old module name. Either:
- Delete `.idea/` and re-import
- Or manually update `.idea/modules.xml` and related files
---
## Variant: Native UI (sharedLogic + sharedUI Split)
For projects where each platform has its own native UI and only business logic is shared:
```
sharedLogic/ # Pure KMP library (no Compose)
build.gradle.kts # kotlin.multiplatform + com.android.kotlin.multiplatform.library
src/
commonMain/kotlin/ # ViewModels, repositories, models, networking
androidMain/kotlin/ # Android-specific implementations
iosMain/kotlin/ # iOS-specific implementations
sharedUI/ # Optional: Compose Multiplatform UI
build.gradle.kts # kotlin.multiplatform + com.android.kotlin.multiplatform.library + compose
src/
commonMain/kotlin/ # Shared composables
androidMain/kotlin/ # Android-specific composables
androidApp/ # Native Android app
build.gradle.kts
src/main/ # Android UI (Compose or XML), depends on sharedLogic (and optionally sharedUI)
iosApp/ # Native iOS app (SwiftUI/UIKit)
# Depends on sharedLogic framework
```
### sharedLogic/build.gradle.kts
```kotlin
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidKmpLibrary)
}
kotlin {
android {
namespace = "com.example.shared.logic"
compileSdk = 35
minSdk = 24
}
iosX64()
iosArm64()
iosSimulatorArm64()
listOf(iosX64(), iosArm64(), iosSimulatorArm64()).forEach {
it.binaries.framework {
baseName = "SharedLogic"
isStatic = true
}
}
sourceSets {
commonMain.dependencies {
implementation(libs.kotlinx.coroutines.core)
implementation(libs.ktor.client.core)
implementation(libs.kotlinx.serialization.json)
}
}
}
```
This variant is useful when:
- iOS uses SwiftUI and does not want Compose Multiplatform
- Desktop is not a target
- You want to minimize the shared surface area
---
## Variant: Server (Backend Module)
For projects that include a Ktor/Spring server:
```
shared/ # KMP library (shared models, API contracts)
androidApp/
iosApp/
server/ # JVM server application
build.gradle.kts # kotlin("jvm") + ktor/spring plugin
src/main/kotlin/
```
### server/build.gradle.kts
```kotlin
plugins {
alias(libs.plugins.kotlinJvm)
alias(libs.plugins.ktor) // or spring boot
application
}
application {
mainClass.set("com.example.server.ApplicationKt")
}
dependencies {
implementation(project(":shared"))
implementation(libs.ktor.server.core)
implementation(libs.ktor.server.netty)
implementation(libs.logback.classic)
}
```
The server module is a plain JVM module. It depends on `:shared` for common models and API contracts. It is unaffected by the AGP 9.0 migration except that:
- If shared previously had a `jvm()` target that the server depended on, verify it still exists after restructuring.
- If shared was renamed, update the dependency path.
---
## settings.gradle.kts -- Final State
```kotlin
rootProject.name = "MyProject"
pluginManagement {
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}
include(":shared")
include(":androidApp")
include(":desktopApp")
include(":webApp")
// include(":server") // if applicable
```
---
## Quick Checklist
- [ ] Create `androidApp/` with pure Android application plugin (see MIGRATION-APP-SPLIT.md)
- [ ] Create `desktopApp/` with compose desktop plugin and `application {}` block
- [ ] Create `webApp/` with wasmJs target and `binaries.executable()`
- [ ] Move `main()` functions from `composeApp/src/{platform}Main/` to respective app modules
- [ ] Move `compose.desktop.application {}` config to `desktopApp`
- [ ] Move `wasmJs { browser {} }` config to `webApp`
- [ ] Rename `composeApp` to `shared`
- [ ] Convert shared to KMP library plugin (`com.android.kotlin.multiplatform.library`)
- [ ] Remove platform app targets from shared (keep only library targets)
- [ ] Update all `settings.gradle.kts` includes
- [ ] Update all `project(":composeApp")` references to `project(":shared")`
- [ ] Update Xcode project (framework name, Gradle task path, Swift imports)
- [ ] Verify each app module builds independently
- [ ] Run all platform targets to confirm functionality

View File

@@ -0,0 +1,561 @@
# Migrating a KMP Library Module to AGP 9.0
This reference covers the full migration of a Kotlin Multiplatform library module from `com.android.library` (AGP 8.x) to `com.android.kotlin.multiplatform.library` (AGP 9.x).
---
## build.gradle.kts -- Before (AGP 8.x)
```kotlin
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidLibrary)
}
kotlin {
androidTarget {
compilations.all {
kotlinOptions { jvmTarget = "11" }
}
}
iosX64()
iosArm64()
iosSimulatorArm64()
sourceSets {
commonMain.dependencies {
implementation(libs.kotlinx.coroutines.core)
}
androidMain.dependencies {
implementation(libs.androidx.appcompat)
}
}
}
android {
namespace = "com.example.shared"
compileSdk = 34
defaultConfig { minSdk = 24 }
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
dependencies {
debugImplementation(libs.compose.ui.tooling)
}
```
## build.gradle.kts -- After (AGP 9.x)
```kotlin
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidKmpLibrary)
}
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()
sourceSets {
commonMain.dependencies {
implementation(libs.kotlinx.coroutines.core)
}
androidMain.dependencies {
implementation(libs.androidx.appcompat)
}
}
}
dependencies {
androidRuntimeClasspath(libs.compose.ui.tooling)
}
```
---
## Version and Plugin Changes
### With Version Catalog (`gradle/libs.versions.toml`)
**Before:**
```toml
[versions]
agp = "8.7.3"
kotlin = "2.1.0"
[plugins]
androidLibrary = { id = "com.android.library", version.ref = "agp" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
```
**After:**
```toml
[versions]
agp = "9.0.1"
kotlin = "2.3.20"
[plugins]
androidKmpLibrary = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
```
### Without Version Catalog
If versions are declared directly in build files, update the plugin IDs and versions in place:
**Before (root build.gradle.kts):**
```kotlin
plugins {
id("com.android.library") version "8.7.3" apply false
id("org.jetbrains.kotlin.multiplatform") version "2.1.0" apply false
}
```
**After (root build.gradle.kts):**
```kotlin
plugins {
id("com.android.kotlin.multiplatform.library") version "9.0.1" apply false
id("org.jetbrains.kotlin.multiplatform") version "2.3.20" apply false
}
```
**Before (module build.gradle.kts):**
```kotlin
plugins {
id("org.jetbrains.kotlin.multiplatform")
id("com.android.library")
}
```
**After (module build.gradle.kts):**
```kotlin
plugins {
id("org.jetbrains.kotlin.multiplatform")
id("com.android.kotlin.multiplatform.library")
}
```
Key changes:
- The plugin ID changes from `com.android.library` to `com.android.kotlin.multiplatform.library`.
- AGP version must be 9.0.0+, Gradle 9.1.0+, KGP 2.0.0+ (2.3.0+ recommended).
---
## Root build.gradle.kts Changes
### With Version Catalog
**Before:**
```kotlin
plugins {
alias(libs.plugins.androidLibrary) apply false
alias(libs.plugins.kotlinMultiplatform) apply false
}
```
**After:**
```kotlin
plugins {
alias(libs.plugins.androidKmpLibrary) apply false
alias(libs.plugins.kotlinMultiplatform) apply false
}
```
### Without Version Catalog
**Before:**
```kotlin
plugins {
id("com.android.library") version "8.7.3" apply false
id("org.jetbrains.kotlin.multiplatform") version "2.1.0" apply false
}
```
**After:**
```kotlin
plugins {
id("com.android.kotlin.multiplatform.library") version "9.0.1" apply false
id("org.jetbrains.kotlin.multiplatform") version "2.3.20" apply false
}
```
No other root-level changes are required unless you have convention plugins that reference the old plugin ID (see convention plugin section below).
---
## Source Directory Renames
The new KMP-integrated plugin does NOT change the expected source directory layout. The standard KMP source sets still apply:
| Source Set | Directory |
|-------------------------|---------------------------|
| `commonMain` | `src/commonMain/kotlin/` |
| `androidMain` | `src/androidMain/kotlin/` |
| `androidMain` resources | `src/androidMain/res/` |
| `iosMain` | `src/iosMain/kotlin/` |
**No renames are required** if you already use the standard KMP layout. If your module previously used the classic Android layout (`src/main/java/`, `src/main/res/`), you must migrate to the KMP layout:
| Old (Android layout) | New (KMP layout) |
|--------------------------------|---------------------------------------|
| `src/main/java/` | `src/androidMain/kotlin/` |
| `src/main/res/` | `src/androidMain/res/` |
| `src/main/AndroidManifest.xml` | `src/androidMain/AndroidManifest.xml` |
| `src/test/java/` | `src/androidHostTest/kotlin/` |
| `src/androidTest/java/` | `src/androidDeviceTest/kotlin/` |
---
## Test Configuration
The new plugin uses explicit opt-in for test source sets.
### Host Tests (Unit Tests)
```kotlin
kotlin {
android {
// Enable unit tests (JVM-based, run on host machine)
withHostTest {
// Optional: configure the host test compilation
}
}
}
```
This creates the `androidHostTest` source set. The previous name `androidUnitTest` still works as an alias but `androidHostTest` is preferred.
### Device Tests (Instrumented Tests)
```kotlin
kotlin {
android {
// Enable instrumented tests (run on device/emulator)
withDeviceTest {
// Optional: configure the device test compilation
}
}
}
```
This creates the `androidDeviceTest` source set. The previous name `androidInstrumentedTest` still works as an alias but `androidDeviceTest` is preferred.
### Full Test Example
```kotlin
kotlin {
android {
namespace = "com.example.shared"
compileSdk = 35
minSdk = 24
withHostTest {}
withDeviceTest {}
}
sourceSets {
getByName("androidHostTest").dependencies {
implementation(libs.junit)
implementation(libs.robolectric)
}
getByName("androidDeviceTest").dependencies {
implementation(libs.androidx.test.runner)
implementation(libs.androidx.test.espresso.core)
}
}
}
```
---
## Java Compilation (withJava)
If your module contains Java source files in `androidMain`, you must explicitly enable Java compilation:
```kotlin
kotlin {
android {
withJava()
}
}
```
Without this call, `.java` files in `src/androidMain/java/` will be ignored. Kotlin files are compiled by default.
---
## Consumer ProGuard Rules
### Before (AGP 8.x)
```kotlin
android {
defaultConfig {
consumerProguardFiles("consumer-rules.pro")
}
}
```
### After (AGP 9.x)
```kotlin
kotlin {
android {
consumerProguardFiles.add(file("consumer-rules.pro"))
}
}
```
**Warning:** Consumer ProGuard rules can be silently dropped during migration if you forget this step. The old `android {}` block is gone, so the `consumerProguardFiles` call in `defaultConfig` has no equivalent location unless you explicitly add it in `kotlin { android {} }`.
---
## JVM Target Configuration Hierarchy
There are three levels at which you can configure the JVM target. They are listed from most specific (highest priority) to least specific (lowest priority):
### Level 1: Android-Specific Compiler Options (Recommended)
```kotlin
kotlin {
android {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
}
}
}
```
This sets the JVM target only for the Android compilation.
### Level 2: Top-Level Kotlin Compiler Options
```kotlin
kotlin {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
}
}
```
This sets the JVM target for ALL JVM-based compilations in the project (Android, JVM desktop, etc.).
### Level 3: Gradle Toolchain
```kotlin
kotlin {
jvmToolchain(11)
}
```
This sets both the JDK used for compilation and the JVM target. It is the broadest setting and affects all JVM compilations.
### Priority Order
If multiple levels are set, the most specific wins:
1. `kotlin { android { compilerOptions { } } }` -- highest priority
2. `kotlin { compilerOptions { } }` -- medium priority
3. `kotlin { jvmToolchain() }` -- lowest priority
### Migration from kotlinOptions
The old `kotlinOptions` DSL is removed:
```kotlin
// REMOVED in AGP 9.0 -- do not use
androidTarget {
compilations.all {
kotlinOptions { jvmTarget = "11" }
}
}
```
Replace with one of the three levels above.
---
## Dependencies Configuration Changes
The top-level `dependencies {}` block configurations change because build variants (debug/release) are removed from the KMP library plugin.
### Before
```kotlin
dependencies {
debugImplementation(libs.compose.ui.tooling)
releaseImplementation(libs.some.lib)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.test.runner)
}
```
### After
```kotlin
dependencies {
// Use string-based configuration names
"androidRuntimeClasspath"(libs.compose.ui.tooling)
// Or use sourceSets for most dependencies
}
kotlin {
sourceSets {
androidMain.dependencies {
implementation(libs.some.lib)
}
getByName("androidHostTest").dependencies {
implementation(libs.junit)
}
getByName("androidDeviceTest").dependencies {
implementation(libs.androidx.test.runner)
}
}
}
```
**Prefer putting dependencies inside `sourceSets` blocks** rather than the top-level `dependencies {}` block. The top-level block is only needed for special configurations like `androidRuntimeClasspath` that have no source set equivalent.
---
## Dependency Resolution Details
When your KMP module depends on a legacy Android library that exposes multiple variants (e.g., `debug`/`release` build types or custom flavor dimensions like `free`/`paid`), you must explicitly define how to resolve them using the `localDependencySelection` DSL.
### Before
```kotlin
android {
defaultConfig {
// The consuming module doesn't have a 'tier' dimension,
// so it tells Gradle to use the 'free' flavor of dependencies
missingDimensionStrategy("tier", "free")
}
buildTypes {
getByName("debug") {
// If the dependency doesn't have a 'debug' build type, fallback to 'release'
matchingFallbacks.add("release")
}
}
}
```
### After
```kotlin
kotlin {
android {
localDependencySelection {
// Determine which build type to consume from Android library dependencies, in order of preference
selectBuildTypeFrom.set(listOf("debug", "release"))
// Map the missing custom flavor dimensions directly
productFlavorDimension("tier") {
selectFrom.set(listOf("free"))
}
}
}
}
```
---
## Android Resources
Android resources (`res/`) are not processed by default with the new plugin. You must explicitly enable them:
```kotlin
kotlin {
android {
androidResources { enable = true }
}
}
```
Without this, files in `src/androidMain/res/` will be ignored and `R` class generation will not happen.
---
## Convention Plugin Refactoring
If you use convention plugins (build-logic), update them:
### Before
```kotlin
// build-logic/convention/src/main/kotlin/KmpLibraryConventionPlugin.kt
class KmpLibraryConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
pluginManager.apply("com.android.library")
pluginManager.apply("org.jetbrains.kotlin.multiplatform")
extensions.configure<LibraryExtension> {
compileSdk = 34
defaultConfig.minSdk = 24
}
}
}
}
```
### After
```kotlin
// build-logic/convention/src/main/kotlin/KmpLibraryConventionPlugin.kt
class KmpLibraryConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
pluginManager.apply("org.jetbrains.kotlin.multiplatform")
pluginManager.apply("com.android.kotlin.multiplatform.library")
extensions.configure<KotlinMultiplatformExtension> {
android {
namespace = // set per-module or pass as parameter
compileSdk = 35
minSdk = 24
}
}
}
}
}
```
The `LibraryExtension` class from AGP is no longer used. All Android configuration goes through `KotlinMultiplatformExtension.android {}`.
---
## Quick Checklist
- [ ] Update plugin IDs and versions (in `libs.versions.toml` if using version catalog, or directly in build files)
- [ ] Replace plugin alias in `build.gradle.kts`
- [ ] Move `android {}` block contents into `kotlin { android {} }`
- [ ] Replace `androidTarget {}` with `android {}`
- [ ] Replace `kotlinOptions` with `compilerOptions`
- [ ] Enable `androidResources` if using Android resources
- [ ] Enable `withHostTest {}` if there are any android host tests or common tests
- [ ] Enable `withDeviceTest {}` if there are any android device tests
- [ ] Add `withJava()` if module contains Java source files
- [ ] Move consumer ProGuard rules to new DSL
- [ ] Migrate top-level `dependencies` to source set dependencies
- [ ] Update convention plugins if applicable
- [ ] Rename test source dirs: `androidUnitTest` to `androidHostTest`, `androidInstrumentedTest` to `androidDeviceTest`
- [ ] Update root `build.gradle.kts` plugin declarations
- [ ] Run `./gradlew :module:assemble` to verify
- [ ] Run `./gradlew :module:testAndroidHostTest` if there are any android host tests or common tests
- [ ] Run `./gradlew :module:assembleAndroidDeviceTest` if there are any android device tests

View File

@@ -0,0 +1,59 @@
# Plugin Compatibility: AGP 9.0
AGP 9.0 introduces breaking changes that affect many third-party plugins. **Before migrating, check
which plugins the project uses and whether they are compatible.**
---
## Known Compatible Plugins (minimum version required)
| Plugin | Minimum Compatible Version | Notes |
|-------------------------------------|----------------------------|--------------------------------------------------------------------------------------------------------------------------------------|
| `com.google.devtools.ksp` | 2.3.1 (2.3.3+ recommended) | 2.3.1 adds AGP 9.0 support; 2.3.3+ fixes deprecated compilerOptions KGP API usage. May need `android.disallowKotlinSourceSets=false` |
| `com.google.dagger.hilt.android` | 2.59 | — |
| `com.google.firebase.firebase-perf` | 2.0.2 | — |
| `androidx.navigation.safeargs` | 2.9.5 | — |
| `org.jetbrains.compose` | 1.9.3 | — |
| `org.jetbrains.dokka` | 2.2.0-Beta | — |
| `app.cash.burst` | 2.10.0 | — |
| `com.google.firebase.testlab` | 0.0.1-alpha11 | — |
---
## Plugins Requiring Opt-Out Flags
These work but require temporarily setting `android.newDsl=false` (or other flags):
| Plugin | Workaround |
|---------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
| `androidx.baselineprofile` (< 1.5.0-alpha01) | `android.newDsl=false` |
| `de.mannodermaus.android-junit5` (< 1.13.4.0) | `android.newDsl=false` |
| `com.google.android.gms:oss-licenses-plugin` (< 0.10.8) | `android.newDsl=false` |
| `com.apollographql.apollo` (< 4.4.0) | `android.newDsl=false` |
| `org.gradle.android.cache-fix` (< 3.0.2) | `android.newDsl=false` |
| `com.github.triplet.play` (< 4.0.0) | `android.newDsl=false` |
| `app.cash.sqldelight` | `android.newDsl=false` + `android.disallowKotlinSourceSets=false` |
| `com.google.protobuf` | `android.newDsl=false` |
| `app.cash.paparazzi` | `android.newDsl=false` |
| `io.gitlab.arturbosch.detekt` (< 2.0.0) | `android.newDsl=false` + `android.builtInKotlin=false` |
| `org.jlleitschuh.gradle.ktlint` | `android.builtInKotlin=false` |
| `dev.icerock.mobile.multiplatform-resources` (< 0.26.0) | `android.builtInKotlin=false` + `android.newDsl=false` + `android.sourceset.disallowProvider=false` |
---
## Known Broken Plugins (No Workaround)
| Plugin | Status |
|------------------------------|---------------------------|
| `com.newrelic.agent.android` | Incompatible with AGP 9.0 |
| `com.huawei.agconnect.agcp` | Incompatible with AGP 9.0 |
---
## What To Do
1. **Inventory all plugins** used in the project
2. **Check each against the tables above**
3. **If any plugin is broken without workaround**, inform the user — they may need to wait for a plugin update or remove it
4. **If plugins need opt-out flags**, add them to `gradle.properties` and note them as temporary workarounds
5. **Update plugin versions** to their AGP 9.0-compatible versions before or during migration

View File

@@ -0,0 +1,132 @@
# Version Compatibility Matrix for KMP AGP 9.0 Migration
---
## Compatibility Table
| Component | Minimum | Recommended | Notes |
|-------------------------------|--------------------|---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| AGP | 9.0.0 | 9.0.1+ | 9.0.0 is the initial release; 9.0.1+ includes early bug fixes |
| Gradle | 9.1.0 | 9.1.0+ | AGP 9.0 requires Gradle 9.1+; earlier Gradle versions will not work |
| JDK | 17 | 17+ | AGP 9.0 requires JDK 17 minimum |
| SDK Build Tools | 36.0.0 | 36.0.0 | Required by AGP 9.0 |
| KGP (Kotlin Gradle Plugin) | 2.0.0 | 2.3.0+ | 2.0.0 is minimum for KMP library plugin; 2.3.0+ has best compatibility |
| KGP (built-in Kotlin runtime) | 2.2.10 | 2.3.0+ | AGP 9.0 has runtime dependency on KGP 2.2.10; auto-upgrades if lower |
| KSP | 2.3.1 | 2.3.6 | KSP version is no longer tied to the Kotlin compiler version since 2.3.0. AGP 9.0 and built-in Kotlin support added in 2.3.1. KSP migrated away from the deprecated compilerOptions KGP API in 2.3.3; earlier versions may have compatibility problems with other Gradle plugins |
| NDK | — | 28.2.13676358 | Default changed to r28c; specify explicitly if needed |
| Android Studio | Otter 3 (2025.2.3) | Latest stable | First version with full AGP 9.0 + KMP library plugin IDE support |
| IntelliJ IDEA | Not supported | — | Does not support AGP 9.0 as of 2026.1, use Android Studio instead. Can still be used for non-Android KMP targets (JVM, iOS, JS/Wasm) |
| Max API Level | — | 36.1 | Highest supported API level in AGP 9.0 |
| Compose Multiplatform | 1.9.3 | 1.10.0+ | AGP 9.0 support was added in 1.9.3 |
| Compose Compiler Plugin | 2.0.0 | Matches KGP version | Since KGP 2.0, use `org.jetbrains.kotlin.plugin.compose` — version is tied to KGP automatically |
| Kotlin Coroutines | 1.8.0 | 1.10.0+ | 1.8.0+ for full K2 support |
| Kotlin Serialization | 1.6.0 | 1.8.0+ | 1.8.0+ for K2 compiler plugin support |
| Ktor | 2.3.0 | 3.0.0+ | 3.0.0 for best KMP library plugin compatibility |
| Room (KMP) | 2.7.0 | 2.8.0+ | KMP Room requires KSP; verify KSP compatibility |
---
## Version Notes
### AGP 9.0.0
- First release supporting `com.android.kotlin.multiplatform.library`.
- Built-in Kotlin compilation for `com.android.application` and `com.android.library` (no separate `kotlin-android` plugin needed).
- Removes support for `com.android.application` + `org.jetbrains.kotlin.multiplatform` in the same module.
- Single-variant model for KMP libraries (no build types/flavors).
- Runtime dependency on KGP 2.2.10 — projects using lower KGP versions are auto-upgraded.
- If the project uses KSP, upgrade to 2.3.1+ for AGP 9.0 support.
- New DSL interfaces only — `BaseExtension` and legacy types removed.
- `org.jetbrains.kotlin.kapt` incompatible — use KSP or `com.android.legacy-kapt`.
- Java source/target default changed from Java 8 to Java 11.
- R class is compile-time non-final in application modules by default.
- `targetSdk` defaults to `compileSdk` when not set (was `minSdk`).
- NDK default changed to r28c.
- Requires JDK 17+, Gradle 9.1.0+, SDK Build Tools 36.0.0.
- Many Gradle property defaults changed — see SKILL.md "Gradle Properties Default Changes".
- Removed: embedded Wear OS app support, density split APKs, legacy variant APIs.
- New: IDE support for test fixtures, fused library plugin (preview).
### AGP 9.0.1+
- Bug fixes for KMP library plugin edge cases.
- Improved error messages for common migration mistakes.
- Better IDE sync performance.
### KGP 2.3.0+
- Best compatibility with KMP AGP 9.0 library plugin.
- Improved multiplatform source set inference.
- Better error diagnostics for KMP configuration issues.
- Stable Compose compiler plugin integration.
---
## Upgrade Path
### From AGP 8.x + KGP 1.9.x
1. Upgrade KGP to 2.0.0+ first (can be done on AGP 8.x).
2. Migrate `kotlinOptions` to `compilerOptions`.
3. Upgrade Gradle to 9.1.0.
4. Upgrade AGP to 9.0.1+.
5. Migrate library plugins to `com.android.kotlin.multiplatform.library`.
6. Upgrade KGP to 2.3.0+ for best experience.
### From AGP 8.x + KGP 2.0.x
1. Upgrade Gradle to 9.1.0.
2. Upgrade AGP to 9.0.1+.
3. Migrate library plugins to `com.android.kotlin.multiplatform.library`.
4. Upgrade KGP to 2.3.0+ for best experience.
---
## gradle/wrapper/gradle-wrapper.properties
```properties
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1-bin.zip
```
---
## Basic libs.versions.toml template
```toml
[versions]
agp = "9.0.1"
kotlin = "2.3.20"
compose-multiplatform = "1.10.3"
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
androidKmpLibrary = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
```
---
## Compatibility Validation Commands
Run these to verify your setup is compatible:
```bash
# Check Gradle version
./gradlew --version
# Check AGP version applied
./gradlew buildEnvironment | grep -e "com.android.library" -e "com.android.application" -e "com.android.kotlin.multiplatform.library"
# Check KGP version
./gradlew buildEnvironment | grep "org.jetbrains.kotlin:kotlin-gradle-plugin"
# Verify the KMP library plugin is recognized
./gradlew :shared:tasks --group=build
# Full validation build
./gradlew :shared:assemble :androidApp:assembleDebug
```