refactor(page): 删除旧版页面文件
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -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
|
||||
|
||||
---
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
Reference in New Issue
Block a user