The Android Target: the Android-KMP Library Plugin and AGP 9
|
This content was generated with the assistance of AI and should be verified against the official Kotlin Multiplatform documentation before being relied on in production. See the section bibliography for the reference material consulted while preparing these pages. |
Every Kotlin Multiplatform (KMP) project that ships to Android needs an Android target, and for years that
target was bolted on by applying com.android.library or com.android.application next to the Kotlin
Multiplatform plugin. Android Gradle Plugin (AGP) 9 ends that arrangement. This page explains the replacement,
the Android-KMP library plugin (com.android.kotlin.multiplatform.library), how it differs from the
plugins you may know from the native Android build, why
the Android app must live in its own androidApp module, and how to migrate an existing project.
The page targets the section baseline: Kotlin 2.4.20, AGP 9.4.1 and Gradle 9.8.0. Kotlin 2.4.20 is
tested against Gradle 7.6.3 to 9.7.0 and AGP 8.5.2 to 9.3.1, so AGP 9.4.1 and Gradle 9.8.0 are slightly newer
than the tested range; the minimum AGP for the new plugin itself is 8.10.0. The running example is
TrailMate Everywhere, whose sharedLogic and sharedUI modules are Android-KMP libraries consumed by an
androidApp module. The Kotlin language is not taught here (see the
Kotlin Reference); neither are Android Gradle fundamentals, which
live in Project Structure, Gradle and Build Variants.
For the multiplatform-wide Gradle DSL see
Gradle Configuration, the Multiplatform DSL and Compatibility.
Why the Android Target Changed
Android is the one target in a KMP project that is not built by Kotlin alone: it is built by AGP, a separate Gradle plugin with its own release train, its own DSL and its own variant model. Historically the two plugins were wired together through an adapter inside the Kotlin plugin.
-
The first adapter used a target named
android. JetBrains later renamed it toandroidTargetso that the nameandroidcould be reserved for a plugin written by the Android team for multiplatform use. -
That plugin is
com.android.kotlin.multiplatform.library, available from AGP 8.10 (the classKotlinMultiplatformAndroidLibraryTargetwas added in AGP 8.8) and configured inside thekotlin {}block, askotlin { android { } }. -
With AGP 9.0 (released Q4 2025) the old combination became unsupported: the Kotlin Multiplatform Gradle plugin is no longer compatible with
com.android.applicationorcom.android.library. The APIs the old combination relied on are deprecated, need an explicit opt-in under AGP 9 and are expected to be removed in AGP 10.
The table summarizes how the three ways of getting an Android target compare.
| Aspect | androidTarget() + com.android.library (legacy) |
com.android.application + KMP plugin (legacy) |
com.android.kotlin.multiplatform.library |
|---|---|---|---|
Role |
Android target of a shared library module |
Android app and shared code in one module |
Android target of a shared library module |
Works with AGP 9 |
Only with legacy variant API enabled |
Not supported |
Yes (designed for it) |
DSL location |
Top-level |
Top-level |
|
Variants |
Build types and product flavors |
Build types and product flavors |
One single variant |
Android tests |
Created by default |
Created by default |
Opt-in |
Java compilation |
On by default |
On by default |
Opt-in with |
Android resources |
On by default |
On by default |
Opt-in with |
Source-set names |
|
Same |
|
Future |
Removal expected in AGP 10 |
Must become a separate |
The supported path |
The plugin is only for library modules. Google states there is no direct replacement for configuring a
Kotlin Multiplatform module with com.android.application; the documented migration is to extract the Android
app into a separate Gradle module. That is the single biggest structural consequence of AGP 9 and the reason the
recommended layout of this section has an androidApp module (see
Project Structure, Targets, Source Sets and the Hierarchy Template).
Before and After AGP 9
The following diagram contrasts the module graph of TrailMate Everywhere in the legacy single-module layout
(typical of projects generated before AGP 9, where one composeApp module held the shared code and the Android
entry point) with the layout that AGP 9 requires.
kotlin-multiplatform
+ com.android.application
shared code + MainActivity"] IOS1["iosApp (Xcode)"] DSK1["desktop entry point"] IOS1 -->|"framework"| CA DSK1 --- CA end subgraph after ["After AGP 9 -- one module per entry point"] direction TB AA["androidApp
com.android.application
MainActivity, manifest, signing"] SU["sharedUI
kotlin-multiplatform
+ com.android.kotlin.multiplatform.library"] SL["sharedLogic
kotlin-multiplatform
+ com.android.kotlin.multiplatform.library"] IOS2["iosApp (Xcode)"] DSK2["desktopApp"] AA --> SU SU --> SL IOS2 -->|"framework"| SU DSK2 --> SU end before ==> after
Three things changed. The Android application plugin moved out of the shared module and into androidApp; the
shared modules switched from com.android.library (or the application plugin) to the Android-KMP library plugin;
and every other platform entry point (iosApp, desktopApp, webApp) stayed an entry point rather than
becoming a Gradle dependency of the shared code.
Applying the Android-KMP Library Plugin
Applying the plugin takes three edits: declare it in the version catalog, register it in the root build file
with apply false so that all subprojects share one plugin classpath, and apply it in each shared module.
# gradle/libs.versions.toml
[versions]
agp = "9.4.1"
kotlin = "2.4.20"
[plugins]
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
android-kotlin-multiplatform-library = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" }
android-application = { id = "com.android.application", version.ref = "agp" }
In the root build.gradle.kts, add the same aliases with apply false.
The shared module then combines the Kotlin Multiplatform plugin with the new Android plugin and configures the
Android target in an android {} block inside kotlin {}:
// sharedLogic/build.gradle.kts
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.android.kotlin.multiplatform.library)
}
kotlin {
android {
namespace = "com.example.trailmate.shared.logic"
compileSdk = 36
minSdk = 24
}
iosArm64()
iosSimulatorArm64()
jvm("desktop")
sourceSets {
commonMain.dependencies {
implementation(libs.kotlinx.coroutines.core)
}
androidMain.dependencies {
implementation(libs.androidx.core.ktx)
}
}
}
There is no top-level android {} extension any more. The Android DSL lives inside the target, which is
why the same configuration reads kotlin { android { namespace = … } }. Some older JetBrains tutorials spell
the block androidLibrary {}; the current Android Developers documentation uses android {}, and so does this
section.
The three properties in the example are the minimum:
| Property | Meaning |
|---|---|
|
The Kotlin/Java package used for the generated |
|
The Android API level the library is compiled against ( |
|
The lowest API level the library supports. Compose Multiplatform requires at least API 21; TrailMate uses 24. |
There is no targetSdk in a library: it is an application concept and belongs to androidApp. There is also no
applicationId, versionCode or versionName, for the same reason.
Dependencies and Source Sets
Android-only dependencies go in the androidMain source set, using the same sourceSets {} block as every
other target. The legacy trick of putting implementation(…) in a top-level dependencies {} block to mean
"Android only" no longer exists in shared modules; it was a frequent source of confusion because it mixed with
commonMainImplementation(…) in the same block.
The plugin creates androidMain and, only on demand, two test source sets:
| Source set (directory) | Compilation | Purpose |
|---|---|---|
|
|
Android implementations, |
|
|
Local JVM unit tests (formerly |
|
|
Instrumented tests on a device or emulator (formerly |
The Single-Variant Model
The most visible difference from com.android.library is that the Android-KMP plugin produces exactly one
variant. There are no build types (debug, release), no product flavors and no flavor dimensions. A KMP
library is published as one .aar that every consumer variant resolves against, so variants of the library
itself add cost without benefit. Dropping them makes configuration simpler and, according to Google, improves
build performance and IDE integration.
The following table lists what disappears with variants and the documented replacement.
Feature of com.android.library |
Replacement in an Android-KMP library |
|---|---|
Build types and product flavors |
Create a separate standalone |
|
Generate constants for all targets with the BuildKonfig community plugin (or a similar solution) |
|
|
Data Binding and View Binding |
Use a multiplatform UI framework such as Compose Multiplatform; these features are implementation details of a final Android app |
|
Use Kotlin/Native |
AIDL and RenderScript |
Not supported; keep them in a standalone Android library module |
|
|
BuildKonfig as the BuildConfig Alternative
TrailMate wants to expose the API base URL and a debug flag to shared code on all platforms. Android’s
BuildConfig cannot do that, so the shared module uses the third-party BuildKonfig plugin. This is a community
plugin, not part of Kotlin or AGP; check its own documentation for the current version and options.
// sharedLogic/build.gradle.kts (excerpt; BuildKonfig is a third-party plugin)
import com.codingfeline.buildkonfig.compiler.FieldSpec.Type
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.android.kotlin.multiplatform.library)
alias(libs.plugins.buildkonfig)
}
buildkonfig {
packageName = "com.example.trailmate"
defaultConfigs {
buildConfigField(Type.STRING, "API_BASE_URL", "https://api.example.com/v1/")
buildConfigField(Type.BOOLEAN, "VERBOSE_LOGGING", "false")
}
}
Shared code then reads BuildKonfig.API_BASE_URL from commonMain on every platform.
If the app (not the library) needs a real BuildConfig with applicationId, versionName and build-type
fields, keep using it in androidApp, which still uses com.android.application and therefore still has build
types and flavors. Only the shared libraries are single-variant.
Selecting Variants of Android Library Dependencies
The library itself has one variant, but it may depend on a standard Android library that has several. The
kotlin.android.localDependencySelection {} block (selectBuildTypeFrom, productFlavorDimension(…))
replaces matchingFallbacks and missingDimensionStrategy for choosing which of their variants is used.
Opt-In Features
To keep builds lean, the plugin disables everything that most shared modules do not need. Each feature is
enabled explicitly inside kotlin { android { } }. The table gives the overview; the subsections show each one.
| Feature | Default | How to enable |
|---|---|---|
Java compilation |
Off |
|
Host (local unit) tests |
Off |
|
Device (instrumented) tests |
Off |
|
Android resources ( |
Off |
|
Consumer keep rules |
Not published |
|
Java Compilation with withJava()
If the Android target contains .java files under src/androidMain/java, call withJava(). It enables Java
compilation for sources in this module only, not for its dependencies. The JVM target is set once in the
target’s compilerOptions and applies to both Kotlin and Java.
// sharedLogic/build.gradle.kts
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
kotlin {
android {
namespace = "com.example.trailmate.shared.logic"
compileSdk = 36
minSdk = 24
withJava() // compile .java files in src/androidMain/java
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}
}
}
The JVM target can be set at three levels, and the more specific level wins: kotlin { jvmToolchain(21) }
(all JVM-based targets, lowest precedence), kotlin { android { compilerOptions { … } } } (all Android
compilations) and the compiler options of a single compilation such as androidHostTest (highest).
The legacy separate compileOptions {} (Java) and kotlinOptions {} (Kotlin) blocks of com.android.library
are replaced by this single setting.
Host and Device Tests
Android unit tests (running on the local JVM) and instrumented tests (running on a device or emulator) are both
disabled by default, so the source sets androidHostTest and androidDeviceTest do not exist until you opt in.
Plain multiplatform tests in commonTest do not need either: they already run as a JVM test task, and
Testing shows how to organize them.
// sharedLogic/build.gradle.kts
kotlin {
android {
// Local JVM tests in src/androidHostTest (formerly androidUnitTest)
withHostTest {
isIncludeAndroidResources = true
}
// Instrumented tests in src/androidDeviceTest (formerly androidInstrumentedTest)
withDeviceTest {
instrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
execution = "HOST"
}
}
sourceSets {
androidHostTest.dependencies {
implementation(libs.robolectric)
}
androidDeviceTest.dependencies {
implementation(libs.androidx.test.runner)
}
}
}
The legacy configuration sets the runner in defaultConfig and the options in testOptions; with the new plugin
both live in the test builder. The default directories move from src/test and src/androidTest to
src/androidHostTest and src/androidDeviceTest. For Android-specific testing techniques (Robolectric,
instrumented tests, Compose test rules) see
Unit Testing with JUnit, Mockito and Robolectric
and Instrumented and UI Testing.
Android Resources
Resource processing (res/ directories, XML drawables, strings.xml) costs build time, so the new plugin
disables it. Enable it only when the module really contains Android resources, for example a vector drawable
used by an Android-specific composable:
// sharedUI/build.gradle.kts
kotlin {
android {
namespace = "com.example.trailmate.shared.ui"
compileSdk = 36
minSdk = 24
androidResources {
enable = true
}
}
}
Resources then go under src/androidMain/res (not src/main/res). If the shared UI uses Compose Multiplatform
resources (composeResources), you do not need Android resources for them; see
Compose Multiplatform Resources.
Consumer Keep Rules
A library that needs R8/ProGuard rules to survive in its consumers' release builds ships them as consumer keep
rules. With com.android.library any file listed in consumerProguardFiles was published automatically. With
the new plugin publishing is explicit:
// sharedLogic/build.gradle.kts
kotlin {
android {
optimization {
consumerKeepRules.apply {
publish = true
file("consumer-proguard-rules.pro")
}
}
}
}
A common mistake is to forget publish = true after migrating, so release builds of the app crash while debug
builds work. For the R8 side see Building, Signing and Releasing.
Tooling Dependencies and androidRuntimeClasspath
Compose Preview, layout inspection and similar IDE tools need androidx.compose.ui:ui-tooling at runtime, but
that dependency must not leak into the published library. In a legacy module you wrote
debugImplementation(…), relying on the debug build type. A single-variant library has no debug build type,
so the dependency is added to the runtime classpath of the main Android compilation instead:
// sharedUI/build.gradle.kts
dependencies {
// Available at runtime for Compose Preview, but not on the compile classpath
// and not part of the library's published API
"androidRuntimeClasspath"(libs.androidx.compose.ui.tooling)
}
The configuration name is quoted because the top-level dependencies {} block has no type-safe accessor for
it. The workaround is available from AGP 9.0.0-beta01. The androidApp module, which is a real application with
a debug build type, keeps using debugImplementation for the same tooling.
Why AGP 9 Forces the androidApp Split
An Android application module needs everything the single-variant library model removes: build types
(debug and release signing), flavors, applicationId, versionCode, manifest placeholders and the
packaging pipeline that produces .apk and .aab. The Android-KMP plugin deliberately has none of that, and
AGP 9 no longer lets com.android.application coexist with the Kotlin Multiplatform plugin in one module. The
consequence is structural and unavoidable:
-
The entry point (
MainActivity, the manifest, launcher icons, signing,applicationId) moves toandroidApp, a plain Android module withcom.android.application. -
The shared code (
sharedLogic,sharedUI) stays multiplatform and uses the Android-KMP library plugin. -
androidAppdepends onsharedUI; the shared code never depends on the app.
This is the same pattern iOS has always had (the Xcode app depends on the Kotlin framework) and desktop and web entry points follow. It also removes a classic problem: a module that was both application and library made the Android entry point a build dependency of everything else. The recommended layout is discussed in Project Structure, Targets, Source Sets and the Hierarchy Template.
Built-In Kotlin
AGP 9.0 ships built-in Kotlin support and enables it by default: an Android module can compile Kotlin without
applying org.jetbrains.kotlin.android (kotlin-android). Two consequences matter for KMP.
-
In
androidApp(a plain Android module) you remove thekotlin-androidplugin when you move to AGP 9. Leaving it applied fails with an error saying the plugin is no longer required. -
In a shared module nothing changes in this respect: you still apply
org.jetbrains.kotlin.multiplatformtogether withcom.android.kotlin.multiplatform.library. Built-in Kotlin replaceskotlin-androidonly, and usingorg.jetbrains.kotlin.multiplatformtogether withcom.android.libraryorcom.android.applicationis no longer allowed once built-in Kotlin is enabled.
The androidApp build script of TrailMate Everywhere therefore looks like this: no Kotlin plugin line, the
Compose compiler plugin for the UI, and a dependency on the shared UI module.
// androidApp/build.gradle.kts
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.compose.compiler) // org.jetbrains.kotlin.plugin.compose
}
android {
namespace = "com.example.trailmate.android"
compileSdk = 36
defaultConfig {
applicationId = "com.example.trailmate"
minSdk = 24
targetSdk = 36
versionCode = 1
versionName = "1.0"
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
}
kotlin {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
}
}
dependencies {
implementation(projects.sharedUI)
implementation(libs.androidx.activity.compose)
debugImplementation(libs.androidx.compose.ui.tooling)
}
The app module uses the ordinary android {} block, with src/main as its source directory, exactly as in a
native Android project; that is the right place for everything described in
Project Structure, Gradle and Build Variants. Other
built-in-Kotlin migration items (kotlin-kapt, android.kotlinOptions {}, kotlin.sourceSets {} in
androidApp) are covered in that Android page and in the migration guide linked in the references.
Migrating an Existing Project
The official recommendation is to migrate in two stages, verifying that the app runs after each, and to update
AGP itself last. The steps below follow JetBrains' guide for the common case of one composeApp module that
holds both the shared code and the Android entry point.
Step 1: Extract the Android App Module
-
Create the
androidAppdirectory with an emptybuild.gradle.ktsand asrcdirectory, and addinclude(":androidApp")tosettings.gradle.kts. -
In
androidApp/build.gradle.ktsapply the application plugin and the Compose plugins, and add a dependency on the old shared module. -
Move the whole Android
android {}block (includingapplicationId,targetSdk,versionCode,versionNameand signing) from the shared module intoandroidApp. -
In the shared module, change
com.android.applicationtocom.android.librarytemporarily and delete the application-only properties fromdefaultConfig {}. -
Move
src/androidMainintoandroidApp/srcand rename it tomain. Keep entry-point code (MainActivity) inandroidApp, but leave allexpectandactualdeclarations in the shared module’s source sets so that every platform sees them. -
Run the new
androidApprun configuration. Android Studio and IntelliJ IDEA normally create it; otherwise edit the old configuration and change its module toandroidApp.
Step 2: Switch the Shared Module to the Android-KMP Plugin
The legacy module used alias(libs.plugins.android.library), kotlin { androidTarget { } }, a top-level
android {} block and debugImplementation(…). After the migration it looks like this:
// sharedUI/build.gradle.kts -- AFTER (Android-KMP library plugin)
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.android.kotlin.multiplatform.library)
}
kotlin {
android {
namespace = "com.example.trailmate.shared.ui"
compileSdk = 36
minSdk = 24
compilerOptions { jvmTarget.set(JvmTarget.JVM_17) }
androidResources { enable = true } // only if the module has res/ files
}
}
dependencies {
"androidRuntimeClasspath"(libs.androidx.compose.ui.tooling)
}
The checklist for this stage:
-
Swap the plugin alias, and register the new plugin in the root build file with
apply false. -
Replace
kotlin { androidTarget { } }withkotlin { android { } }and delete the top-levelandroid {}block. -
Move
compileSdkandminSdk(andnamespace) into the new block; droptargetSdkandapplicationId. -
Replace
debugImplementation(…)with"androidRuntimeClasspath"(…). -
Opt in to
androidResources,withJava(), tests and keep rules wherever the module relied on the old defaults. -
Rename test directories:
src/androidUnitTesttosrc/androidHostTest,src/androidInstrumentedTesttosrc/androidDeviceTest, and legacysrc/testandsrc/androidTestlikewise. -
Give the library a
namespacethat differs from the app’s namespace.
Step 3: Upgrade AGP and Gradle
Finally raise the versions: AGP to a 9.x release (this section uses 9.4.1), and the Gradle wrapper to at least
the minimum that the AGP release requires (JetBrains' guide mentions 9.1.0 for AGP 9.0; this section uses
Gradle 9.8.0). Remove kotlin-android from androidApp because Kotlin support is now built in, then run a full
build.
The recommended order matters: doing the module split and plugin swap before bumping AGP keeps each failure attributable. If the build breaks only after the AGP bump, it is an AGP 9 issue; if it breaks after the split, it is a module wiring issue.
Escape Hatch: android.enableLegacyVariantApi
If you cannot finish the migration yet, AGP 9 can be told to keep the deprecated variant APIs alive so that the old plugin combination keeps working in the short term:
# gradle.properties
android.enableLegacyVariantApi=true
This is a stopgap, not a destination. The legacy APIs are scheduled to be removed in AGP 10, expected in the second half of 2026. Treat the flag as a deadline.
Android Studio supports AGP 9 from Otter 3 Feature Drop (2025.2.3) onward, so keep the IDE current before migrating. See Getting Started: IDEs, the KMP Plugin and Your First Project.
The androidTarget to android Rename
The name of the Android target DSL has changed twice, which causes real confusion when reading older snippets.
| Kotlin version | Change |
|---|---|
Originally |
An |
2.1.0 |
Deprecation warning raised to an error |
2.2.0 |
|
2.3.0 |
The new plugin is available; deprecation warning for |
2.3.10 |
The |
The practical reading: in a module with com.android.library, androidTarget() is still the (legacy) spelling;
in a module with com.android.kotlin.multiplatform.library, the target is declared by the plugin and
configured as kotlin { android { } }. Writing androidTarget() next to the new plugin is a common mistake and
does not work. Existing androidTarget code on AGP 9 also needs the legacy variant API opt-in described above.
The Legacy Android Source-Set Layout Is Gone
Before Kotlin 1.9.0 the Kotlin plugin named source sets after AGP’s: androidAndroidTest, androidTest, a
src/main/kotlin directory and so on. The new Android source-set layout (introduced in Kotlin 1.8.0, default
since 1.9.0) names them androidMain, androidUnitTest and androidInstrumentedTest and uses src/androidMain/kotlin
and src/androidMain/AndroidManifest.xml. The deprecation cycle of the old layout ended in Kotlin 2.4.0:
| Kotlin version | Behavior of kotlin.mpp.androidSourceSetLayoutVersion=1 |
|---|---|
1.9.0 and earlier |
Warning (suppressible) |
1.9.20 |
Error, not suppressible |
2.4.0 |
Support for the legacy layout and the property removed |
If a project that still sets that property is upgraded to Kotlin 2.4.20, delete the property and apply the
rename table: main to androidMain, test to androidUnitTest, androidTest to androidInstrumentedTest,
and move the manifest from src/main/AndroidManifest.xml to src/androidMain/AndroidManifest.xml. A related
change: in the new layout androidInstrumentedTest no longer implicitly depends on commonTest; if you want
common tests to run as instrumented tests you must add dependsOn(commonTest) yourself. (With the Android-KMP
plugin these two source sets are androidHostTest and androidDeviceTest, as described above.)
Custom Gradle Plugins That React to the Android Target
Teams that centralize configuration in convention plugins (see
Project Structure, Gradle and Build Variants for build-logic)
must update them: the new plugin is not part of com.android.base, so checks like
plugins.withId("com.android.base") no longer detect it, and its extension is not registered at the project
level but lives inside the target. Google’s guide for plugin authors recommends reacting to the Kotlin
Multiplatform plugin first and then to the target types:
// build-logic/convention/src/main/kotlin/TrailMateAndroidKmpConventionPlugin.kt
import com.android.build.api.dsl.KotlinMultiplatformAndroidLibraryTarget
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
class TrailMateAndroidKmpConventionPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.plugins.withId("com.android.kotlin.multiplatform.library") {
val kmp = project.extensions.getByType(KotlinMultiplatformExtension::class.java)
// The android {} block is a target that also acts as the Android DSL extension
kmp.targets.withType(KotlinMultiplatformAndroidLibraryTarget::class.java).configureEach { androidTarget ->
androidTarget.compileSdk = 36
androidTarget.minSdk = 24
}
}
}
}
Rules from the guide worth remembering:
-
Use
plugins.withId(…)rather than checking immediately, so the plugin is not sensitive to plugin application order. -
Use
targets.configureEach { }(lazily) rather thanforEach, which can run before targets are registered. -
To distinguish legacy from new integration, check
target is KotlinAndroidTarget(old) versustarget is KotlinMultiplatformAndroidLibraryTarget(new). The latter class was added in AGP 8.8, so guard the check withAndroidPluginVersion.getCurrent()if your plugin may run on older AGP. -
The Android compilations of the new target are
main,hostTestanddeviceTest(instances ofKotlinMultiplatformAndroidCompilation). -
Do not call
withHostTest {}orwithDeviceTest {}just to set defaults in a convention plugin, because that eagerly creates the test components. -
androidComponents.onVariants {}still works, but it iterates over a single variant, and build-type and flavor properties are not available onAndroidKotlinMultiplatformVariant.
For the Kotlin side of convention plugins and version catalogs in a multiplatform build, see Gradle Configuration, the Multiplatform DSL and Compatibility.
Common Mistakes
| Mistake | Fix |
|---|---|
Applying |
Use |
Keeping a top-level |
Move everything into |
Using |
Remove it; the plugin provides the target |
Expecting |
BuildKonfig for constants; a standalone Android library module for variants |
|
|
Tests or |
They are opt-in: |
Release crashes after migration because keep rules vanished |
Set |
Leaving |
Remove it; Kotlin is built in |