refactor(page): 删除旧版页面文件
This commit is contained in:
494
.agents/skills/kotlin-tooling-agp9-migration/SKILL.md
Normal file
494
.agents/skills/kotlin-tooling-agp9-migration/SKILL.md
Normal file
@@ -0,0 +1,494 @@
|
||||
---
|
||||
name: kotlin-tooling-agp9-migration
|
||||
description: >
|
||||
Migrates Kotlin Multiplatform (KMP) projects to Android Gradle Plugin 9.0+.
|
||||
Handles plugin replacement (com.android.kotlin.multiplatform.library), module
|
||||
splitting, DSL migration, and the new default project structure. Use when
|
||||
upgrading AGP, when build fails due to KMP+AGP incompatibility, or when the
|
||||
user mentions AGP 9.0, android multiplatform plugin, KMP migration, or
|
||||
com.android.kotlin.multiplatform.library.
|
||||
license: Apache-2.0
|
||||
metadata:
|
||||
author: JetBrains
|
||||
version: "1.0.0"
|
||||
---
|
||||
|
||||
# KMP AGP 9.0 Migration
|
||||
|
||||
Android Gradle Plugin 9.0 makes the Android application and library plugins incompatible
|
||||
with the Kotlin Multiplatform plugin in the same module. This skill guides you through the
|
||||
migration.
|
||||
|
||||
## Step 0: Analyze the Project
|
||||
|
||||
Before making any changes, understand the project structure:
|
||||
1. Read `settings.gradle.kts` (or `.gradle`) to find all modules
|
||||
2. For each module, read its `build.gradle.kts` to identify which plugins are applied
|
||||
3. Check if the project uses a Gradle version catalog (`gradle/libs.versions.toml`). If it exists,
|
||||
read it for current AGP/Gradle/Kotlin versions. If not, find versions directly in `build.gradle.kts`
|
||||
files (typically in the root `buildscript {}` or `plugins {}` block). **Adapt all examples in this
|
||||
guide accordingly** — version catalog examples use `alias(libs.plugins.xxx)` while direct usage
|
||||
uses `id("plugin.id") version "x.y.z"`
|
||||
4. Read `gradle/wrapper/gradle-wrapper.properties` for the Gradle version
|
||||
5. Check `gradle.properties` for any existing workarounds (`android.enableLegacyVariantApi`)
|
||||
6. Check for `org.jetbrains.kotlin.android` plugin usage — AGP 9.0 has built-in Kotlin and this plugin must be removed
|
||||
7. Check for `org.jetbrains.kotlin.kapt` plugin usage — incompatible with built-in Kotlin, must migrate to KSP or `com.android.legacy-kapt`
|
||||
8. Check for third-party plugins that may be incompatible with AGP 9.0 (see "Plugin Compatibility" section below)
|
||||
|
||||
If Bash is available, run `scripts/analyze-project.sh` from this skill's directory to get a structured summary.
|
||||
|
||||
### Classify Each Module
|
||||
|
||||
For each module, determine its type:
|
||||
|
||||
| Current plugins | Migration path |
|
||||
|--------------------------------------------------------------------------|---------------------------------------------|
|
||||
| `kotlin.multiplatform` + `com.android.library` | **Path A** — Library plugin swap |
|
||||
| `kotlin.multiplatform` + `com.android.application` | **Path B** — Mandatory Android split |
|
||||
| `kotlin.multiplatform` with multiple platform entry points in one module | **Path C** — Full restructure (recommended) |
|
||||
| `com.android.application` or `com.android.library` (no KMP) | See "Pure Android Tips" below |
|
||||
|
||||
### Determine Scope
|
||||
|
||||
- **Path B is mandatory** for any module combining KMP + Android application plugin
|
||||
- **Path C is recommended** when the project has a monolithic `composeApp` (or similar) module
|
||||
containing entry points for multiple platforms (Android, Desktop, Web). This aligns with the
|
||||
new JetBrains default project structure where each platform gets its own app module.
|
||||
- **Ask the user** whether they want Path B only (minimum required) or Path C (recommended full restructure)
|
||||
|
||||
## Path A: Library Module Migration
|
||||
|
||||
Use this when a module applies `kotlin.multiplatform` + `com.android.library`.
|
||||
|
||||
See [references/MIGRATION-LIBRARY.md](references/MIGRATION-LIBRARY.md) for full before/after code.
|
||||
|
||||
Summary:
|
||||
|
||||
1. **Replace plugin**: `com.android.library` → `com.android.kotlin.multiplatform.library`
|
||||
2. **Remove `org.jetbrains.kotlin.android`** plugin if present (AGP 9.0 has built-in Kotlin support)
|
||||
3. **Migrate DSL**: Move config from top-level `android {}` block into `kotlin { android {} }`:
|
||||
```kotlin
|
||||
kotlin {
|
||||
android {
|
||||
namespace = "com.example.lib"
|
||||
compileSdk = 35
|
||||
minSdk = 24
|
||||
}
|
||||
}
|
||||
```
|
||||
4. **Rename source directories** (only if the module uses classic Android layout instead of KMP layout):
|
||||
- `src/main` → `src/androidMain`
|
||||
- `src/test` → `src/androidHostTest`
|
||||
- `src/androidTest` → `src/androidDeviceTest`
|
||||
- If the module already uses `src/androidMain/`, no directory renames are needed
|
||||
5. **Move dependencies** from top-level `dependencies {}` into `sourceSets`:
|
||||
```kotlin
|
||||
kotlin {
|
||||
sourceSets {
|
||||
androidMain.dependencies {
|
||||
implementation("androidx.appcompat:appcompat:1.7.0")
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
6. **Enable resources** explicitly if the module uses Android or Compose Multiplatform resources:
|
||||
```kotlin
|
||||
kotlin {
|
||||
android {
|
||||
androidResources { enable = true }
|
||||
}
|
||||
}
|
||||
```
|
||||
7. **Enable Java** compilation if module has `.java` source files:
|
||||
```kotlin
|
||||
kotlin {
|
||||
android {
|
||||
withJava()
|
||||
}
|
||||
}
|
||||
```
|
||||
8. **Enable tests** explicitly if the module has unit or instrumented tests:
|
||||
```kotlin
|
||||
kotlin {
|
||||
android {
|
||||
withHostTest { isIncludeAndroidResources = true }
|
||||
withDeviceTest {
|
||||
instrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
9. **Update Compose tooling dependency**:
|
||||
```kotlin
|
||||
// Old:
|
||||
debugImplementation(libs.androidx.compose.ui.tooling)
|
||||
// New:
|
||||
androidRuntimeClasspath(libs.androidx.compose.ui.tooling)
|
||||
```
|
||||
10. **Publish consumer ProGuard rules** explicitly if applicable:
|
||||
```kotlin
|
||||
kotlin {
|
||||
android {
|
||||
consumerProguardFiles.add(file("consumer-rules.pro"))
|
||||
}
|
||||
}
|
||||
```
|
||||
11. **Resolve Sub-dependency Variants (Product Flavors / Build Types)**:
|
||||
Because the new KMP Android library plugin enforces a single-variant architecture, it does not natively understand how to resolve dependencies that publish multiple variants (like `debug`/`release` build types, or product flavors like `free`/`paid`). Configure fallback behaviors using `localDependencySelection`:
|
||||
```kotlin
|
||||
kotlin {
|
||||
android {
|
||||
localDependencySelection {
|
||||
// Determine which build type to consume from Android library dependencies, in order of preference
|
||||
selectBuildTypeFrom.set(listOf("debug", "release"))
|
||||
|
||||
// If the dependency has a 'tier' dimension, select the 'free' flavor
|
||||
productFlavorDimension("tier") {
|
||||
selectFrom.set(listOf("free"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Path B: Android App + Shared Module Split
|
||||
|
||||
Use this when a module applies `kotlin.multiplatform` + `com.android.application`. This is **mandatory** for AGP 9.0 compatibility.
|
||||
|
||||
See [references/MIGRATION-APP-SPLIT.md](references/MIGRATION-APP-SPLIT.md) for full guide.
|
||||
|
||||
Summary:
|
||||
|
||||
1. **Create `androidApp` module** with its own `build.gradle.kts`:
|
||||
```kotlin
|
||||
plugins {
|
||||
alias(libs.plugins.androidApplication)
|
||||
// Do NOT apply kotlin-android — AGP 9.0 includes Kotlin support
|
||||
alias(libs.plugins.composeMultiplatform) // if using Compose
|
||||
alias(libs.plugins.composeCompiler) // if using Compose
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.app"
|
||||
compileSdk = 35
|
||||
defaultConfig {
|
||||
applicationId = "com.example.app"
|
||||
minSdk = 24
|
||||
targetSdk = 35
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
}
|
||||
buildFeatures { compose = true }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.shared) // or whatever the shared module is named
|
||||
implementation(libs.androidx.activity.compose)
|
||||
}
|
||||
```
|
||||
2. **Move Android entry point code** from `src/androidMain/` to `androidApp/src/main/`:
|
||||
- `MainActivity.kt` (and any other Activities/Fragments)
|
||||
- `AndroidManifest.xml` (app-level manifest with `<application>` and launcher `<activity>`) — verify `android:name` on `<activity>` uses the fully qualified class name in its new location
|
||||
- Android Application class if present
|
||||
- App-level resources (launcher icons, theme, etc.)
|
||||
3. **Add to `settings.gradle.kts`**: `include(":androidApp")`
|
||||
4. **Add to root `build.gradle.kts`**: plugin declarations with `apply false`
|
||||
5. **Convert original module** from application to library using Path A steps
|
||||
6. **Ensure different namespaces**: app module and library module must have distinct namespaces
|
||||
7. **Remove from shared module**: `applicationId`, `targetSdk`, `versionCode`, `versionName`
|
||||
8. **Update IDE run configurations**: change the module from the old module to `androidApp`
|
||||
|
||||
## Path C: Full Restructure (Recommended)
|
||||
|
||||
Use this when the project has a monolithic module (typically `composeApp`) containing entry
|
||||
points for multiple platforms. This is optional but aligns with the new JetBrains default.
|
||||
|
||||
See [references/MIGRATION-FULL-RESTRUCTURE.md](references/MIGRATION-FULL-RESTRUCTURE.md) for full guide.
|
||||
|
||||
### Target Structure
|
||||
|
||||
```
|
||||
project/
|
||||
├── shared/ ← KMP library (was composeApp), pure shared code
|
||||
├── androidApp/ ← Android entry point only
|
||||
├── desktopApp/ ← Desktop entry point only (if desktop target exists)
|
||||
├── webApp/ ← Wasm/JS entry point only (if web target exists)
|
||||
├── iosApp/ ← iOS Xcode project (usually already separate)
|
||||
└── ...
|
||||
```
|
||||
|
||||
### Steps
|
||||
|
||||
1. **Apply Path B first** — extract `androidApp` (mandatory for AGP 9.0)
|
||||
2. **Extract `desktopApp`** (if desktop target exists):
|
||||
- Create module with `org.jetbrains.compose` and `application {}` plugin
|
||||
- Move `main()` function from `desktopMain` to `desktopApp/src/main/kotlin/`
|
||||
- Move `compose.desktop { application { ... } }` config to `desktopApp/build.gradle.kts`
|
||||
- Add dependency on `shared` module
|
||||
3. **Extract `webApp`** (if wasmJs/js target exists):
|
||||
- Create module with appropriate Kotlin/JS or Kotlin/Wasm configuration
|
||||
- Move web entry point from `wasmJsMain`/`jsMain` to `webApp/src/wasmJsMain/kotlin/`
|
||||
- Move browser/distribution config to `webApp/build.gradle.kts`
|
||||
- Add dependency on `shared` module
|
||||
4. **iOS** — typically already in a separate `iosApp` directory. Verify:
|
||||
- Framework export config (`binaries.framework`) stays in `shared` module
|
||||
- Xcode project references the correct framework path
|
||||
5. **Rename module** from `composeApp` to `shared`:
|
||||
- Rename directory
|
||||
- Update `settings.gradle.kts` include
|
||||
- Update all dependency references across modules
|
||||
6. **Clean up shared module**: remove all platform entry point code and app-specific config
|
||||
that was moved to the platform app modules
|
||||
|
||||
### Variant: Native UI
|
||||
|
||||
If some platforms use native UI (e.g., SwiftUI for iOS), split `shared` into:
|
||||
- `sharedLogic` — business logic consumed by ALL platforms
|
||||
- `sharedUI` — Compose Multiplatform UI consumed only by platforms using shared UI
|
||||
|
||||
### Variant: Server
|
||||
|
||||
If the project includes a server target:
|
||||
- Add `server` module at the root
|
||||
- Move all client modules under an `app/` directory
|
||||
- Add `core` module for code shared between server and client (models, validation)
|
||||
|
||||
## Version Updates
|
||||
|
||||
These are required regardless of migration path:
|
||||
|
||||
1. **Gradle wrapper** — update to 9.1.0+:
|
||||
```properties
|
||||
# gradle/wrapper/gradle-wrapper.properties
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
|
||||
```
|
||||
2. **AGP version** — update to 9.0.0+ and add the KMP library plugin.
|
||||
|
||||
With version catalog (`gradle/libs.versions.toml`):
|
||||
```toml
|
||||
[versions]
|
||||
agp = "9.0.1"
|
||||
|
||||
[plugins]
|
||||
android-kotlin-multiplatform-library = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" }
|
||||
```
|
||||
|
||||
Without version catalog — update `com.android.*` plugin versions and add in root `build.gradle.kts`:
|
||||
```kotlin
|
||||
plugins {
|
||||
id("com.android.application") version "9.0.1" apply false
|
||||
id("com.android.kotlin.multiplatform.library") version "9.0.1" apply false
|
||||
}
|
||||
```
|
||||
3. **JDK** — ensure JDK 17+ is used (required by AGP 9.0)
|
||||
4. **SDK Build Tools** — update to 36.0.0:
|
||||
```
|
||||
Install via SDK Manager or configure in android { buildToolsVersion = "36.0.0" }
|
||||
```
|
||||
5. **Review gradle.properties** — remove error-causing properties and review changed defaults (see "Gradle Properties Default Changes" section)
|
||||
|
||||
## Built-in Kotlin Migration
|
||||
|
||||
AGP 9.0 enables built-in Kotlin support by default for all `com.android.application` and `com.android.library`
|
||||
modules. The `org.jetbrains.kotlin.android` plugin is no longer needed and will conflict if applied.
|
||||
|
||||
**Important:** Built-in Kotlin does NOT replace KMP support. KMP library modules still need
|
||||
`org.jetbrains.kotlin.multiplatform` + `com.android.kotlin.multiplatform.library`.
|
||||
|
||||
### Step 1: Remove kotlin-android Plugin
|
||||
|
||||
Remove from **all** module-level and root-level build files:
|
||||
|
||||
```kotlin
|
||||
// Remove from module build.gradle.kts
|
||||
plugins {
|
||||
// REMOVE: alias(libs.plugins.kotlin.android)
|
||||
// REMOVE: id("org.jetbrains.kotlin.android")
|
||||
}
|
||||
|
||||
// Remove from root build.gradle.kts
|
||||
plugins {
|
||||
// REMOVE: alias(libs.plugins.kotlin.android) apply false
|
||||
}
|
||||
```
|
||||
|
||||
Remove from version catalog (`gradle/libs.versions.toml`):
|
||||
```toml
|
||||
[plugins]
|
||||
# REMOVE: kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
```
|
||||
|
||||
### Step 2: Migrate kapt to KSP or legacy-kapt
|
||||
|
||||
The `org.jetbrains.kotlin.kapt` plugin is **incompatible** with built-in Kotlin.
|
||||
|
||||
**Preferred: Migrate to KSP** — see the KSP migration guide for each annotation processor.
|
||||
|
||||
**Fallback: Use `com.android.legacy-kapt`** (same version as AGP):
|
||||
```toml
|
||||
# gradle/libs.versions.toml
|
||||
[plugins]
|
||||
legacy-kapt = { id = "com.android.legacy-kapt", version.ref = "agp" }
|
||||
```
|
||||
```kotlin
|
||||
// Module build.gradle.kts — replace kotlin-kapt with legacy-kapt
|
||||
plugins {
|
||||
// REMOVE: alias(libs.plugins.kotlin.kapt)
|
||||
alias(libs.plugins.legacy.kapt)
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Migrate kotlinOptions to compilerOptions
|
||||
|
||||
For pure Android modules (non-KMP), migrate `android.kotlinOptions {}` to the top-level
|
||||
`kotlin.compilerOptions {}`:
|
||||
```kotlin
|
||||
// Old
|
||||
android {
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
languageVersion = "2.0"
|
||||
freeCompilerArgs += listOf("-Xopt-in=kotlin.RequiresOptIn")
|
||||
}
|
||||
}
|
||||
|
||||
// New
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
|
||||
languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
|
||||
optIn.add("kotlin.RequiresOptIn")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** With built-in Kotlin, `jvmTarget` defaults to `android.compileOptions.targetCompatibility`, so it may be optional if you already set `compileOptions`.
|
||||
|
||||
### Step 4: Migrate kotlin.sourceSets to android.sourceSets
|
||||
|
||||
With built-in Kotlin, only `android.sourceSets {}` with the `kotlin` set is supported:
|
||||
```kotlin
|
||||
// NOT SUPPORTED with built-in Kotlin:
|
||||
kotlin.sourceSets.named("main") {
|
||||
kotlin.srcDir("additionalSourceDirectory/kotlin")
|
||||
}
|
||||
|
||||
// Correct:
|
||||
android.sourceSets.named("main") {
|
||||
kotlin.directories += "additionalSourceDirectory/kotlin"
|
||||
}
|
||||
```
|
||||
|
||||
For generated sources, use the Variant API:
|
||||
```kotlin
|
||||
androidComponents.onVariants { variant ->
|
||||
variant.sources.kotlin!!.addStaticSourceDirectory("additionalSourceDirectory/kotlin")
|
||||
}
|
||||
```
|
||||
|
||||
### Per-Module Migration Strategy
|
||||
|
||||
For large projects, migrate module-by-module:
|
||||
|
||||
1. Disable globally: `android.builtInKotlin=false` in `gradle.properties`
|
||||
2. Enable per migrated module by applying the opt-in plugin:
|
||||
```kotlin
|
||||
plugins {
|
||||
id("com.android.built-in-kotlin") version "AGP_VERSION"
|
||||
}
|
||||
```
|
||||
3. Follow Steps 1-4 for that module
|
||||
4. Once all modules are migrated, remove `android.builtInKotlin=false` and all `com.android.built-in-kotlin` plugins
|
||||
|
||||
### Optional: Disable Kotlin for Non-Kotlin Modules
|
||||
|
||||
For modules that contain **no Kotlin sources**, disable built-in Kotlin to save build time:
|
||||
```kotlin
|
||||
android {
|
||||
enableKotlin = false
|
||||
}
|
||||
```
|
||||
|
||||
### Opt-Out (Temporary)
|
||||
|
||||
If blocked by plugin incompatibilities, opt out temporarily:
|
||||
```properties
|
||||
# gradle.properties
|
||||
android.builtInKotlin=false
|
||||
android.newDsl=false # also required if using new DSL opt-out
|
||||
```
|
||||
|
||||
**Warning:** Ask the user if they want to opt out, and if so, remind them this is a temporary measure.
|
||||
|
||||
## Plugin Compatibility
|
||||
|
||||
See [references/PLUGIN-COMPATIBILITY.md](references/PLUGIN-COMPATIBILITY.md) for the full compatibility table with known compatible versions, opt-out flag workarounds, and broken plugins.
|
||||
|
||||
**Before migrating**, inventory all plugins in the project and check each against that table. If any plugin is broken without workaround, inform the user. If plugins need opt-out flags, add them to`gradle.properties` and note them as temporary workarounds.
|
||||
|
||||
## Gradle Properties Default Changes
|
||||
|
||||
AGP 9.0 changes the defaults for many Gradle properties. Check `gradle.properties` for any explicitly set values that may now conflict.
|
||||
Key changes:
|
||||
|
||||
| Property | Old Default | New Default | Action |
|
||||
|------------------------------------------------------|-------------|-------------|---------------------------------------------------|
|
||||
| `android.uniquePackageNames` | `false` | `true` | Ensure each library has a unique namespace |
|
||||
| `android.enableAppCompileTimeRClass` | `false` | `true` | Refactor `switch` on R fields to `if/else` |
|
||||
| `android.defaults.buildfeatures.resvalues` | `true` | `false` | Enable `resValues = true` where needed |
|
||||
| `android.defaults.buildfeatures.shaders` | `true` | `false` | Enable shaders where needed |
|
||||
| `android.r8.optimizedResourceShrinking` | `false` | `true` | Review R8 keep rules |
|
||||
| `android.r8.strictFullModeForKeepRules` | `false` | `true` | Update keep rules to be explicit |
|
||||
| `android.proguard.failOnMissingFiles` | `false` | `true` | Remove invalid ProGuard file references |
|
||||
| `android.r8.proguardAndroidTxt.disallowed` | `false` | `true` | Use `proguard-android-optimize.txt` only |
|
||||
| `android.r8.globalOptionsInConsumerRules.disallowed` | `false` | `true` | Remove global options from library consumer rules |
|
||||
| `android.sourceset.disallowProvider` | `false` | `true` | Use `Sources` API on androidComponents |
|
||||
| `android.sdk.defaultTargetSdkToCompileSdkIfUnset` | `false` | `true` | Specify `targetSdk` explicitly |
|
||||
| `android.onlyEnableUnitTestForTheTestedBuildType` | `false` | `true` | Only if testing non-default build types |
|
||||
|
||||
Check for and remove properties that now cause errors:
|
||||
- `android.r8.integratedResourceShrinking` — removed, always on
|
||||
- `android.enableNewResourceShrinker.preciseShrinking` — removed, always on
|
||||
|
||||
## Pure Android Tips
|
||||
|
||||
For non-KMP Android modules upgrading to AGP 9.0, follow the "Built-in Kotlin Migration" steps above,
|
||||
then review the "Gradle Properties Default Changes" table. Additional changes:
|
||||
|
||||
- **Review new DSL interfaces** — `BaseExtension` is removed; use `CommonExtension` or specific extension types
|
||||
- **Java default changed** from Java 8 to Java 11 — ensure `compileOptions` reflects this
|
||||
|
||||
## Verification
|
||||
|
||||
After migration, verify with the [checklist](assets/checklist.md). Key checks:
|
||||
|
||||
1. `./gradlew build` succeeds with no errors
|
||||
2. All platform targets build successfully (Android, iOS via `xcodebuild`, Desktop, JS/Wasm)
|
||||
3. `./gradlew :shared:allTests` and Android unit tests pass
|
||||
4. No `com.android.library` or `com.android.application` in KMP modules
|
||||
5. No `org.jetbrains.kotlin.android` in AGP 9.0 modules
|
||||
6. Source sets use correct names (`androidMain`, `androidHostTest`, `androidDeviceTest`)
|
||||
7. No deprecation warnings about variant API or DSL
|
||||
|
||||
## Common Issues
|
||||
|
||||
See [references/KNOWN-ISSUES.md](references/KNOWN-ISSUES.md) for details. Key gotchas:
|
||||
|
||||
### KMP Library Plugin Issues
|
||||
- **BuildConfig unavailable** in library modules — use DI/`AppConfiguration` interface, or use [BuildKonfig](https://github.com/yshrsmz/BuildKonfig) or [gradle-buildconfig-plugin](https://github.com/gmazzo/gradle-buildconfig-plugin) for compile-time constants
|
||||
- **No build variants** — single variant architecture; compile-time constants can use BuildKonfig/gradle-buildconfig-plugin flavors, but variant-specific dependencies/resources/signing must move to app module
|
||||
- **NDK/JNI unsupported** in new plugin — extract to separate `com.android.library` module
|
||||
- **Compose resources crash** without `androidResources { enable = true }`
|
||||
- **Consumer ProGuard rules silently dropped** if not migrated to `consumerProguardFiles.add(file(...))` in new DSL
|
||||
- **KSP** requires version 2.3.1+ for AGP 9.0 compatibility
|
||||
|
||||
### AGP 9.0 General Issues
|
||||
- **BaseExtension removed** — convention plugins using old DSL types need rewriting to use `CommonExtension`
|
||||
- **Variant APIs removed** — `applicationVariants`, `libraryVariants`, `variantFilter` replaced by `androidComponents`
|
||||
- **Convention plugins** need refactoring — old `android {}` extension helpers are obsolete
|
||||
|
||||
## Reference Files
|
||||
|
||||
- [DSL Reference](references/DSL-REFERENCE.md) — side-by-side old→new DSL mapping
|
||||
- [Version Matrix](references/VERSION-MATRIX.md) — AGP/Gradle/KGP/Compose/IDE compatibility
|
||||
- [Plugin Compatibility](references/PLUGIN-COMPATIBILITY.md) — third-party plugin status and workarounds
|
||||
@@ -0,0 +1,53 @@
|
||||
# KMP AGP 9.0 Migration Verification Checklist
|
||||
|
||||
Use this checklist after migration to verify everything is configured correctly.
|
||||
|
||||
## Plugin Configuration
|
||||
- [ ] `com.android.kotlin.multiplatform.library` plugin declared for KMP library modules
|
||||
- [ ] No `com.android.library` or `com.android.application` in KMP modules' build.gradle.kts
|
||||
- [ ] `org.jetbrains.kotlin.android` removed from all build files and version catalog (built-in Kotlin replaces it)
|
||||
- [ ] No `org.jetbrains.kotlin.kapt` plugin — migrated to KSP or `com.android.legacy-kapt`
|
||||
- [ ] `android.kotlinOptions {}` migrated to `kotlin { compilerOptions {} }` (non-KMP modules)
|
||||
- [ ] `kotlin.sourceSets` migrated to `android.sourceSets` with `.kotlin` accessor (non-KMP modules)
|
||||
- [ ] No `android.builtInKotlin=false` unless required by incompatible plugin (documented as temporary)
|
||||
- [ ] Third-party plugins verified compatible
|
||||
|
||||
## KMP Library Modules
|
||||
- [ ] Source sets renamed: `androidMain`, `androidHostTest`, `androidDeviceTest`
|
||||
- [ ] No `android {}` top-level block — use `androidLibrary {}` inside `kotlin {}` instead
|
||||
- [ ] `androidResources { enable = true }` present if module uses Android or Compose Multiplatform resources
|
||||
- [ ] `withJava()` present if module has .java source files
|
||||
- [ ] Tests configured: `withHostTest {}`, `withDeviceTest {}`
|
||||
- [ ] No `debugImplementation` or analogs in library modules
|
||||
- use `androidRuntimeClasspath` for tooling deps
|
||||
- app modules can still use `debugImplementation`
|
||||
- [ ] Unique `namespace` for each library module (different from app module; `android.uniquePackageNames=true` is default in AGP 9.0)
|
||||
|
||||
## Gradle Properties & DSL
|
||||
- [ ] No removed properties in `gradle.properties` that cause errors:
|
||||
- `android.enableLegacyVariantApi`
|
||||
- `android.r8.integratedResourceShrinking`
|
||||
- `android.enableNewResourceShrinker.preciseShrinking`
|
||||
- [ ] Any opt-out flags (`android.newDsl=false`, `android.builtInKotlin=false`) documented with reason
|
||||
- [ ] `targetSdk` explicitly set in all app modules (defaults to `compileSdk` now, was `minSdk`)
|
||||
|
||||
## Build Logic / Convention Plugins
|
||||
- [ ] No references to `BaseExtension`, `AppExtension`, `LibraryExtension` (removed in AGP 9.0)
|
||||
- [ ] Using `CommonExtension` or specific new DSL types
|
||||
- [ ] No use of removed APIs: `applicationVariants`, `libraryVariants`, `variantFilter`
|
||||
|
||||
## ProGuard / R8
|
||||
- [ ] Consumer ProGuard rules migrated to `consumerProguardFiles.add(file(...))` in new DSL
|
||||
- [ ] Using `proguard-android-optimize.txt` (not `proguard-android.txt`)
|
||||
- [ ] No global options (`-dontobfuscate`, `-dontoptimize`) in library consumer rules
|
||||
- [ ] Keep rules updated for R8 strict full mode (explicit default constructor rules if needed)
|
||||
|
||||
## Build & Test Verification
|
||||
- [ ] `./gradlew build` succeeds
|
||||
- [ ] `./gradlew :androidApp:assembleDebug` succeeds (if app module exists)
|
||||
- [ ] `xcodebuild -project iosApp/*.xcodeproj -scheme <scheme> -sdk iphonesimulator build` succeeds (if iOS app exists)
|
||||
- [ ] Desktop app compiles: `./gradlew :desktopApp:run` or equivalent (if desktop target exists)
|
||||
- [ ] Web/Wasm target compiles: `./gradlew :wasmJsApp:wasmJsBrowserDistribution` or equivalent (if web target exists)
|
||||
- [ ] `./gradlew :shared:allTests` succeeds (or equivalent for KMP test tasks)
|
||||
- [ ] `./gradlew :androidApp:testDebugUnitTest` succeeds (if app module exists)
|
||||
- [ ] No deprecation warnings about variant API or DSL
|
||||
@@ -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
|
||||
```
|
||||
@@ -0,0 +1,228 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# analyze-project.sh - Analyze a Gradle/KMP project for AGP 9.0 migration readiness
|
||||
#
|
||||
# Usage: ./analyze-project.sh [PROJECT_ROOT]
|
||||
# Defaults to current directory if PROJECT_ROOT is not specified.
|
||||
|
||||
set -e
|
||||
|
||||
PROJECT_ROOT="${1:-.}"
|
||||
|
||||
# Resolve to absolute path
|
||||
PROJECT_ROOT="$(cd "$PROJECT_ROOT" && pwd)"
|
||||
|
||||
echo "========================================"
|
||||
echo " KMP AGP 9.0 Migration - Project Analysis"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "Project root: $PROJECT_ROOT"
|
||||
echo ""
|
||||
|
||||
# --- Gradle Version ---
|
||||
echo "----------------------------------------"
|
||||
echo " Gradle Version"
|
||||
echo "----------------------------------------"
|
||||
WRAPPER_PROPS="$PROJECT_ROOT/gradle/wrapper/gradle-wrapper.properties"
|
||||
if [ -f "$WRAPPER_PROPS" ]; then
|
||||
GRADLE_URL=$(grep 'distributionUrl' "$WRAPPER_PROPS" | sed 's/.*=//' | sed 's/\\//g')
|
||||
GRADLE_VERSION=$(echo "$GRADLE_URL" | sed 's|.*gradle-||' | sed 's|-.*||')
|
||||
echo " Distribution URL: $GRADLE_URL"
|
||||
echo " Gradle version: $GRADLE_VERSION"
|
||||
else
|
||||
echo " WARNING: gradle-wrapper.properties not found"
|
||||
GRADLE_VERSION="unknown"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# --- AGP Version ---
|
||||
echo "----------------------------------------"
|
||||
echo " Android Gradle Plugin Version"
|
||||
echo "----------------------------------------"
|
||||
TOML_FILE="$PROJECT_ROOT/gradle/libs.versions.toml"
|
||||
if [ -f "$TOML_FILE" ]; then
|
||||
AGP_VERSION=$(grep '^agp' "$TOML_FILE" | head -1 | sed 's/.*= *"//' | sed 's/".*//')
|
||||
if [ -n "$AGP_VERSION" ]; then
|
||||
echo " AGP version (from version catalog): $AGP_VERSION"
|
||||
else
|
||||
echo " AGP version not found in version catalog"
|
||||
AGP_VERSION="unknown"
|
||||
fi
|
||||
else
|
||||
echo " WARNING: libs.versions.toml not found"
|
||||
AGP_VERSION="unknown"
|
||||
fi
|
||||
|
||||
KOTLIN_VERSION=$(grep '^kotlin' "$TOML_FILE" 2>/dev/null | head -1 | sed 's/.*= *"//' | sed 's/".*//')
|
||||
if [ -n "$KOTLIN_VERSION" ]; then
|
||||
echo " Kotlin version: $KOTLIN_VERSION"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# --- Module Analysis ---
|
||||
echo "----------------------------------------"
|
||||
echo " Module Analysis"
|
||||
echo "----------------------------------------"
|
||||
|
||||
# Find all build.gradle.kts and build.gradle files
|
||||
BUILD_FILES=$(find "$PROJECT_ROOT" -name "build.gradle.kts" -o -name "build.gradle" | grep -v '.gradle/' | grep -v 'build/' | sort)
|
||||
|
||||
for BUILD_FILE in $BUILD_FILES; do
|
||||
REL_PATH=$(echo "$BUILD_FILE" | sed "s|$PROJECT_ROOT/||")
|
||||
MODULE_DIR=$(dirname "$BUILD_FILE")
|
||||
REL_MODULE=$(echo "$MODULE_DIR" | sed "s|$PROJECT_ROOT||" | sed 's|^/||')
|
||||
|
||||
if [ -z "$REL_MODULE" ]; then
|
||||
MODULE_NAME="(root)"
|
||||
else
|
||||
MODULE_NAME=":$(echo "$REL_MODULE" | sed 's|/|:|g')"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo " Module: $MODULE_NAME"
|
||||
echo " File: $REL_PATH"
|
||||
|
||||
# Detect plugins
|
||||
HAS_ANDROID_APP="no"
|
||||
HAS_ANDROID_LIB="no"
|
||||
HAS_KMP="no"
|
||||
HAS_KOTLIN_ANDROID="no"
|
||||
HAS_COMPOSE="no"
|
||||
HAS_APPLY_FALSE="no"
|
||||
|
||||
if grep -q 'com.android.application\|androidApplication' "$BUILD_FILE"; then
|
||||
if grep -q 'apply false' "$BUILD_FILE" 2>/dev/null; then
|
||||
HAS_APPLY_FALSE="yes"
|
||||
else
|
||||
HAS_ANDROID_APP="yes"
|
||||
fi
|
||||
fi
|
||||
|
||||
if grep -q 'com.android.library\|androidLibrary' "$BUILD_FILE"; then
|
||||
if grep -q 'apply false' "$BUILD_FILE" 2>/dev/null; then
|
||||
HAS_APPLY_FALSE="yes"
|
||||
else
|
||||
HAS_ANDROID_LIB="yes"
|
||||
fi
|
||||
fi
|
||||
|
||||
if grep -q 'kotlin.multiplatform\|kotlin("multiplatform")\|kotlinMultiplatform' "$BUILD_FILE"; then
|
||||
HAS_KMP="yes"
|
||||
fi
|
||||
|
||||
if grep -q 'kotlin.android\|kotlin("android")\|kotlinAndroid' "$BUILD_FILE"; then
|
||||
HAS_KOTLIN_ANDROID="yes"
|
||||
fi
|
||||
|
||||
if grep -q 'org.jetbrains.compose\|composeMultiplatform' "$BUILD_FILE"; then
|
||||
HAS_COMPOSE="yes"
|
||||
fi
|
||||
|
||||
echo " Plugins detected:"
|
||||
[ "$HAS_ANDROID_APP" = "yes" ] && echo " - com.android.application"
|
||||
[ "$HAS_ANDROID_LIB" = "yes" ] && echo " - com.android.library"
|
||||
[ "$HAS_KMP" = "yes" ] && echo " - kotlin.multiplatform"
|
||||
[ "$HAS_KOTLIN_ANDROID" = "yes" ] && echo " - kotlin.android"
|
||||
[ "$HAS_COMPOSE" = "yes" ] && echo " - org.jetbrains.compose"
|
||||
[ "$HAS_APPLY_FALSE" = "yes" ] && echo " - (declarations with apply false — root buildscript)"
|
||||
|
||||
# Check for android {} block
|
||||
HAS_ANDROID_BLOCK="no"
|
||||
if grep -q '^android {' "$BUILD_FILE" || grep -q '^android {' "$BUILD_FILE"; then
|
||||
HAS_ANDROID_BLOCK="yes"
|
||||
echo " Has android {} block: yes"
|
||||
fi
|
||||
|
||||
# Check source set layout
|
||||
if [ -d "$MODULE_DIR/src/main" ]; then
|
||||
echo " Source layout: src/main (legacy Android)"
|
||||
fi
|
||||
if [ -d "$MODULE_DIR/src/androidMain" ]; then
|
||||
echo " Source layout: src/androidMain (KMP)"
|
||||
fi
|
||||
if [ -d "$MODULE_DIR/src/commonMain" ]; then
|
||||
echo " Source layout: src/commonMain (KMP)"
|
||||
fi
|
||||
|
||||
# Determine migration recommendation
|
||||
echo " Migration recommendation:"
|
||||
if [ "$HAS_APPLY_FALSE" = "yes" ]; then
|
||||
echo " -> Root buildscript: update plugin versions only"
|
||||
elif [ "$HAS_KMP" = "yes" ] && [ "$HAS_ANDROID_LIB" = "yes" ]; then
|
||||
echo " -> Replace com.android.library with android-kotlin-multiplatform-library"
|
||||
echo " -> Move android {} config into androidTarget {} in kotlin {} block"
|
||||
echo " -> Remove the standalone android {} block"
|
||||
elif [ "$HAS_KMP" = "yes" ] && [ "$HAS_ANDROID_APP" = "yes" ]; then
|
||||
echo " -> Split into separate androidApp module (com.android.application)"
|
||||
echo " -> Convert shared KMP module to use android-kotlin-multiplatform-library"
|
||||
echo " -> Move Android entry point (Activity) to the new androidApp module"
|
||||
elif [ "$HAS_ANDROID_APP" = "yes" ] && [ "$HAS_KMP" = "no" ]; then
|
||||
echo " -> Pure Android app module: update AGP to 9.x, no KMP migration needed"
|
||||
elif [ "$HAS_ANDROID_LIB" = "yes" ] && [ "$HAS_KMP" = "no" ]; then
|
||||
echo " -> Pure Android library: update AGP to 9.x, no KMP migration needed"
|
||||
echo " -> (Consider converting to KMP if cross-platform is desired)"
|
||||
elif [ "$HAS_KOTLIN_ANDROID" = "yes" ]; then
|
||||
echo " -> Replace org.jetbrains.kotlin.android with kotlin.multiplatform if going KMP"
|
||||
echo " -> Or keep as-is and just update AGP version"
|
||||
else
|
||||
echo " -> No Android plugins detected: no AGP migration needed"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
# --- Gradle Properties Check ---
|
||||
echo "----------------------------------------"
|
||||
echo " Gradle Properties"
|
||||
echo "----------------------------------------"
|
||||
GRADLE_PROPS="$PROJECT_ROOT/gradle.properties"
|
||||
if [ -f "$GRADLE_PROPS" ]; then
|
||||
LEGACY_FLAGS=""
|
||||
if grep -q 'android.enableLegacyVariantApi' "$GRADLE_PROPS"; then
|
||||
LEGACY_FLAGS="$LEGACY_FLAGS\n - android.enableLegacyVariantApi (must be removed for AGP 9.0)"
|
||||
fi
|
||||
if grep -q 'android.useAndroidX' "$GRADLE_PROPS"; then
|
||||
LEGACY_FLAGS="$LEGACY_FLAGS\n - android.useAndroidX (default in AGP 9.0, can be removed)"
|
||||
fi
|
||||
if grep -q 'android.enableJetifier' "$GRADLE_PROPS"; then
|
||||
LEGACY_FLAGS="$LEGACY_FLAGS\n - android.enableJetifier (removed in AGP 9.0, must be removed)"
|
||||
fi
|
||||
if grep -q 'android.nonTransitiveRClass' "$GRADLE_PROPS"; then
|
||||
LEGACY_FLAGS="$LEGACY_FLAGS\n - android.nonTransitiveRClass (default in AGP 9.0, can be removed)"
|
||||
fi
|
||||
|
||||
if [ -n "$LEGACY_FLAGS" ]; then
|
||||
echo " Legacy flags found:"
|
||||
printf "$LEGACY_FLAGS\n"
|
||||
else
|
||||
echo " No legacy flags found"
|
||||
fi
|
||||
else
|
||||
echo " No gradle.properties file found"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# --- Summary ---
|
||||
echo "========================================"
|
||||
echo " Summary"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo " Current AGP version: $AGP_VERSION"
|
||||
echo " Current Gradle version: $GRADLE_VERSION"
|
||||
echo " Target AGP version: 9.0.0+"
|
||||
echo " Target Gradle version: 9.1.0+"
|
||||
echo ""
|
||||
|
||||
if [ "$AGP_VERSION" != "unknown" ]; then
|
||||
AGP_MAJOR=$(echo "$AGP_VERSION" | cut -d. -f1)
|
||||
if [ "$AGP_MAJOR" -ge 9 ] 2>/dev/null; then
|
||||
echo " Status: Project appears to already be on AGP 9.0+"
|
||||
else
|
||||
echo " Status: Project needs migration from AGP $AGP_VERSION to 9.0+"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " Run the migration skill for guided assistance."
|
||||
echo "========================================"
|
||||
Reference in New Issue
Block a user