blob: 6963fb536a7e83bdc7e46429a16702de2fed28d8 [file] [view] [edit]
# Android builds use the new Android Gradle Plugin DSL and Variant APIs
*Draft breaking-change page for `docs.flutter.dev/release/breaking-changes/`.
This file is the source of truth until the page is published to
flutter/website; publishing must complete before the newDsl flip reaches the
beta channel. Contributor-facing details live in
[Migrating-Flutter-Gradle-Plugin-to-AGP-public-API.md](Migrating-Flutter-Gradle-Plugin-to-AGP-public-API.md).*
## Summary
The Flutter Gradle Plugin now uses only the public Android Gradle Plugin (AGP)
API, and new and migrated Flutter projects build with AGP's new DSL enabled
(`android.newDsl` is no longer set to `false` by Flutter). Gradle build
scripts that use the legacy AGP APIs — most commonly
`android.applicationVariants` — fail to configure and must be migrated to the
AGP Variant API.
## Background
AGP 9 deprecated the legacy DSL and Variant APIs behind the
`android.newDsl=false` flag. AGP 10 removes them entirely. Flutter previously
added `android.newDsl=false` to your `gradle.properties` (via the project
templates and an automatic migration) to keep legacy builds working. That
opt-out stops working with AGP 10, so Flutter has migrated its own Gradle
plugin to the public API and removed the opt-out from templates. A migration
now *removes* the opt-out lines that Flutter previously added — it only touches
lines carrying Flutter's marker comments, and prints a message when it does.
Opt-outs you added by hand are left alone.
`android.builtInKotlin=false` is **not** affected by this change. It is owned
by the separate built-in-Kotlin migration (tracked in
[flutter/flutter#184836](https://github.com/flutter/flutter/issues/184836)),
which means one more (smaller) `gradle.properties` change later.
## Migration guide
### Renaming APKs (`applicationVariants.all`)
Before:
```groovy
android {
applicationVariants.all { variant ->
variant.outputs.all { output ->
outputFileName = "myapp-${variant.versionName}.apk"
}
}
}
```
After (Variant API, `build.gradle` / `build.gradle.kts`):
```kotlin
// Note: androidComponents is a top-level block, peer to android {}
androidComponents {
onVariants(selector().all()) { variant ->
variant.outputs.forEach { output ->
// WARNING: VariantOutput in the new API does not have an outputFileName property.
// You cannot mutate the filename here.
}
}
}
```
For output *file* renames, you must copy or rename the built APKs using a Gradle task.
(Note: Flutter's own copy step already places APKs at `build/app/outputs/flutter-apk/app[-abi][-flavor]-<mode>.apk` with unchanged names and paths.)
Example of a simple finalizer task in `build.gradle` (Groovy):
```groovy
androidComponents {
onVariants(selector().all()) { variant ->
def copyTask = tasks.register("copy${variant.name.capitalize()}Apk", Copy) {
from(variant.artifacts.get(com.android.build.api.artifact.SingleArtifact.APK.INSTANCE))
into(layout.buildDirectory.dir("custom-outputs"))
rename { String fileName -> fileName.replace("app", "myapp") }
}
tasks.matching { it.name == "assemble${variant.name.capitalize()}" }.configureEach {
dependsOn(copyTask)
}
}
}
```
### Setting per-ABI or per-variant versionCode
Before:
```groovy
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
output.versionCodeOverride = abiCodes.get(output.getFilter(OutputFile.ABI)) * 1000 + variant.versionCode
}
}
```
After:
```kotlin
androidComponents {
onVariants(selector().all()) { variant ->
variant.outputs.forEach { output ->
val abi = output.filters.find { it.filterType == FilterConfiguration.FilterType.ABI }?.identifier
val base = output.versionCode.orNull ?: 1
output.versionCode.set((abiCodes[abi] ?: 0) * 1000 + base)
}
}
}
```
Note: Flutter itself sets per-ABI version codes for `--split-per-abi` inside
`onVariants`. If your CI mutates version codes in `afterEvaluate`, that runs at
a different time than before; Flutter prints a warning when it detects a
divergence between the DSL value and the final output value. To verify your
mutations worked, inspect the built APK:
`apkanalyzer manifest print versionCode build/app/outputs/flutter-apk/app-release.apk`
### Custom build types and plugins
Flutter copies your app's custom build types onto Flutter plugin projects so
they resolve. With the new DSL these are `initWith` copies rather than live
aliases:
- Set `matchingFallbacks` on custom build types so dependent Android libraries
resolve. If a third-party plugin does not define your app's custom `staging`
build type, AGP needs to know to safely fall back to compiling the plugin's
`debug` variant rather than failing the build. For example:
```kotlin
android {
buildTypes {
create("staging") {
initWith(getByName("debug"))
matchingFallbacks += listOf("debug", "release")
}
}
}
```
- **Warning for Plugin Authors / JNI Developers:** Library (plugin) projects cannot
be explicitly marked debuggable through the public AGP API. If an app uses a
custom build type (like `staging`), the plugin's `BuildConfig.DEBUG` and native (C++/JNI)
code may silently compile in release mode instead of debug mode. Variant matching
still works via `matchingFallbacks`, but C++ debugging will be broken for those
custom build types.
### Add-to-app (Flutter module in a host app)
- Flutter no longer looks up or configures the host `:app` project from the
module. The dependency between your host's asset merging and Flutter's asset
copy is expressed through the Variant API instead of an explicit
`merge<Variant>Assets.dependsOn(...)` edge. Build scripts that reference
Flutter's `copyFlutterAssets<Variant>` tasks by name or type will break.
For example, `tasks.getByPath(":flutter:copyFlutterAssetsDebug")` will crash
your build with a `Task with path ... not found` error because the
tasks are now registered lazily and are no longer of type
`org.gradle.api.tasks.Copy`.
- `flutter.hostAppProjectName` in `gradle.properties` is now a no-op. Flutter
prints a deprecation warning naming the removal milestone. It was only used
for the host-project lookup, which no longer exists.
- Flutter maps host build types to Flutter build modes using the public
"debuggable" flag: `profile` stays `profile`, debuggable build types map to
`debug`, everything else maps to `release`. If your host has no `profile`
build type, add `matchingFallbacks`:
```kotlin
create("staging") {
initWith(getByName("debug"))
isDebuggable = true // staging gets debug Flutter artifacts
matchingFallbacks += listOf("debug", "release")
}
```
### Flutter plugin authors
- Do not read `android.applicationVariants` / `android.libraryVariants` in
plugin build scripts; use `androidComponents.onVariants`. (We are tracking
an audit of the top 200 plugins for this legacy usage in
[flutter/flutter#190845](https://github.com/flutter/flutter/issues/190845)).
- Do not assume Flutter's tasks exist at configuration time or have specific
types; look up tasks lazily (`tasks.named`) without a type, or better, wire
through Variant API artifacts.
- Test your plugin's example app with AGP 9+ **without** `android.newDsl=false`.
### `flutter build aar`
Variant enumeration for AAR builds now uses the public `components` API. If
your module's build script declares `singleVariant(...)` publishing itself,
Flutter detects the overlap and reports it with an actionable error instead of
failing inside AGP.
## Escape hatch (temporary)
If you cannot migrate immediately, add the opt-out by hand to
`android/gradle.properties`:
```properties
android.newDsl=false
```
**AGP 10 is expected to remove the legacy APIs**, meaning this opt-out will
stop working. Treat it as a short-term unblock only; hand-added opt-outs are
never touched by Flutter's migrator.
## References
- AGP 9 release notes (new DSL):
https://developer.android.com/build/releases/agp-9-0-0-release-notes
- Flutter umbrella issues:
[flutter/flutter#180137](https://github.com/flutter/flutter/issues/180137),
[flutter/flutter#166550](https://github.com/flutter/flutter/issues/166550)