Sitelet https://github.com/flutter/flutter/pull/193693
Skip to content

[AGP 9.1.0 Migration #7] Migrate the flutter-apk copy and per-ABI versionCode to the variant API - #193693

Open
reidbaker-agent wants to merge 3 commits into
flutter:masterfrom
reidbaker-agent:agp-apk-copy-versioncode
Open

reidbaker-agent wants to merge 3 commits into
flutter:masterfrom
reidbaker-agent:agp-apk-copy-versioncode

Conversation

@reidbaker-agent

@reidbaker-agent reidbaker-agent commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Description

This is PR 7 of 11 in the AGP 9.1.0 / public gradle-api migration stack (#180137, #166550).

This pr changes the way splitting by api version codes work. This can be a surprising change but I have not found a work around.

PR 8 adds add-to-app support and as a result deletes a bunch of code that exists to bridge the partial migration. See #19371 specifically https://github.com/flutter/flutter/pull/193717/changes/f668780fd47caca13c03828f49d21572074825d6..a6be16034c8a1a4a0456cd35194b5973cfd4dc75#r4185414230 which is a subset of changes.

Agent authored details

Changes

  1. flutter-apk copy on the variant API. CopyFlutterApksTask (copyFlutterApks<Variant>) copies the variant's SingleArtifact.APK outputs, read through BuiltArtifactsLoader, into build/app/outputs/flutter-apk/. The names are app[-abi][-flavor]-<mode>.apk, the format listApkPaths in lib/src/android/gradle.dart expects. assemble<Variant> depends on the task.

    • The task has no Project or variant fields. It uses injected FileSystemOperations and @InputDirectory @PathSensitive(RELATIVE).
    • It is annotated @DisableCachingByDefault(because = "Not worth caching"), the annotation and reason Gradle uses on its own Copy and Sync tasks.
    • Its declared outputs are the individual APK files (@OutputFiles). The shared flutter-apk directory is not declared, because it would overlap between variants.
    • One naming function, CopyFlutterApksTask.apkFileName, produces both the declared outputs and the copied files. Its inputs are the ABIs of the variant's outputs, the flavor, and the build mode.
  2. Per-ABI versionCode without reading output.versionCode. For --split-per-abi, the plugin sets output.versionCode = ABI_VERSION[abi] * 1000 + base on each ApplicationVariant output with an ABI filter. It never reads the property, because AGP rejects that read during configuration when android.compatibility.enableLegacyApi=false.

    • DslVersionCodes takes a snapshot of defaultConfig.versionCode and each product flavor's versionCode in androidComponents.finalizeDsl.
    • forVariant(variant.productFlavors) resolves the base the way AGP merges it: the first flavor, in dimension order, that sets a versionCode, otherwise defaultConfig.
    • If the DSL declares no versionCode, Flutter logs a warning and leaves the variant's versionCodes unchanged.
  3. APK transform errors point at the transform. The copy fails in two cases. Both can happen only if another plugin or build script transforms SingleArtifact.APK:

    • the APK directory has no output-metadata.json;
    • an APK has an ABI filter that none of the variant's outputs declares.

    Both error messages name SingleArtifact.APK and tell the user to contact the maintainer of the plugin that does the transform. They do not ask for a Flutter issue. The task KDoc and website-page-draft.md say what a transform must keep. Plugins Flutter developers commonly use (Walle, VasDolly, packer-ng, AndResGuard, Sentry, Firebase App Distribution, Gradle Play Publisher) do not trigger either error; the evidence is in this review reply.

  4. Deleted the app-path applicationVariants.configureEach block and configureAbiVersionCodeOverride, together with their two @Suppress("DEPRECATION") markers and the unsafe as ApkVariantOutput cast. The add-to-app module path (addFlutterDepsForModule, libraryVariants) is left for PR 8.

  5. Structure follows the PR 6 review (@mboetger's point that registerFlutterAssetTasks should not register the compile task). onVariants calls configureSplitPerAbiVersionCodes and registerCopyFlutterApksTask after registerFlutterAssetTasks, under the same isApplicationProject && shouldCompileFlutterForVariant gate. Each helper does one job, and no helper takes a parameter only to pass it on.

  6. Docs:

    • Migrating-Flutter-Gradle-Plugin-to-AGP-public-API.md: decision record 4, the replacement map, and items 2 and 3 of "Features that must break".
    • website-page-draft.md: versionCode recipes that do not read output.versionCode, and the rules for APK transforms.
    • Links to the draft carry a TODO for #193713, which tracks publishing it on docs.flutter.dev.
  7. Tests:

    • CopyFlutterApksTaskTest (new, 5 tests).
    • DslVersionCodesTest (new, 5 tests).
    • 8 new FlutterPluginTest cases: defaultConfig and flavor offsets, the no-versionCode warning, no split, the force flag, copy wiring, profile/custom build type naming, and assemble wiring. The mocked VariantOutput.versionCode stubs only set, so any read fails the test.
    • flutter_build_apk_split_per_abi_test.dart: a fourth test builds with android.compatibility.enableLegacyApi=false, and every test asserts each per-ABI APK is in flutter-apk.

Review response (commit 33f39876b92)

Comment Change
Caching annotation: "How do I know this is true?" Uses Gradle's own Copy/Sync annotation and reason (checked with javap).
Why would load return null? / How would ABIs mismatch? Comments say exactly when each can happen. Errors point at the plugin that transforms the APK.
Other places that build APK names FGP has one. The tool's three reading sites are listed in the reply (ratchet: not changed here).
Custom variants, and proof the copy works Build-mode rule matches the deleted code. Results of a flavor and custom build type scratch app are in the reply.
Temporal wording Removed from KDoc and docs.
Link to the website docs TODO tied to #193713 until the page is published.
Reading output.versionCode depends on compatibility mode No read. finalizeDsl snapshot instead, with a compatibility-mode-off integration test.
Why ×10000, and can the earlier numbers be kept? Explained in the replies. Keeping them requires reading the value the app set, which AGP rejects with compatibility mode off.

Why removing the deleted code is safe

The deleted code used AbstractAppExtension.applicationVariants, ApkVariantOutput.versionCodeOverride/getFilter, and packageApplicationProvider. AGP deprecated these, and they are not available with AGP 9's android.newDsl=true. The replacements (ApplicationVariant.outputs, VariantOutput.versionCode/filters, SingleArtifact.APK, BuiltArtifactsLoader, DslLifecycle.finalizeDsl) are public com.android.build.api APIs available at the stack's AGP 8.11.1 floor.

Behavioral and compatibility notes

  1. versionCode for apps that transform it in their own onVariants (breaking). Flutter registers its onVariants callback when the plugin is applied, so AGP runs it before an app's androidComponents.onVariants block. versionCodeOverride was applied after that block. Apps that set versionCode only in the android {} DSL see no change. An app that multiplies by 10000 (compatibility mode on), built with --build-number 42:

    ABI (offset) versionCodeOverride This PR
    armeabi-v7a (1) 421000 10420000
    arm64-v8a (2) 422000 20420000
    x86_64 (4) 424000 40420000

    Values stay distinct, keep ABI order, and only go up, so Play upgrades keep working. With compatibility mode off, apps can't read output.versionCode either. For that case the docs give a recipe that sets an absolute value in the app's onVariants, which replaces Flutter's: offset * 1000 + flutter.versionCode * 10000.

  2. A versionCode declared only in the manifest is not offset. The DSL snapshot can't see it, so Flutter logs a warning and leaves the variant's versionCodes unchanged.

  3. A finalizeDsl callback registered after Flutter's that changes versionCode is not seen. This is documented in the DslVersionCodes KDoc and the migration doc.

  4. copyFlutterApks<Variant> tasks appear in gradlew tasks and can be UP-TO-DATE.

  5. Two build types that map to the same Flutter mode share a file name. For example, debug and a debuggable staging both write app-debug.apk; the deleted code had the same clash. Running both assembles in one ./gradlew invocation makes the two copy tasks alternate. The flutter tool builds one variant per invocation, so it is unaffected.

Integration test coverage of the changed paths

Path Tests
Per-ABI versionCode: default, build number, app onVariants transform, compatibility mode off flutter_build_apk_split_per_abi_test.dart (4 tests; checks versionCodes with apkanalyzer and the per-ABI files in flutter-apk)
flutter-apk copy, default and flavored android_gradle_asset_merging_test.dart, gradle_libapp_so_packaging_test.dart (incl. flavors), android_plugin_example_app_build_test.dart, isolated/native_assets_flutter_build_test.dart; devicelab flavors_test, gradle_desugar_classes_test
Plugin unit tests on AGP 8.11.1 and AGP 9 android_run_flutter_gradle_plugin_tests_test.dart

Tests run locally

  • ./gradlew test in packages/flutter_tools/gradle (JDK 17): pass (33 suites, 0 failures).
  • ktlint 1.5.0 with the CI .editorconfig and baseline: clean.
  • dart format / dart analyze --fatal-infos on the changed Dart test: clean.
  • flutter_build_apk_split_per_abi_test on 33f39876b92: 4/4 pass.
  • On the review changes before the final edits to comments and error messages: android_gradle_asset_merging_test (4), gradle_libapp_so_packaging_test (4), android_run_flutter_gradle_plugin_tests_test (2) all pass.
  • Manual build with enableLegacyApi=false (AGP 9.3.1): versionCodes 1042/2042/4042. A negative control that re-adds the read fails with "configuration of project ':app' has not completed yet".
  • Scratch app with free/paid flavors and a custom staging build type: APK names and versionCodes match the deleted code.

Pre-launch Checklist

@github-actions github-actions Bot added platform-android Android applications specifically tool Affects the "flutter" command-line tool. See also t: labels. d: docs/ flutter/flutter/docs, for contributors labels Oct 2, 2026
The application path of the Flutter Gradle Plugin no longer uses the
deprecated BaseVariant API (applicationVariants / ApkVariantOutput):

- CopyFlutterApksTask copies the variant's SingleArtifact.APK outputs,
  read through BuiltArtifactsLoader, into build/outputs/flutter-apk under
  the unchanged names app[-abi][-flavor]-<mode>.apk. assemble<Variant>
  depends on it. The task has no Project or variant fields and uses an
  injected FileSystemOperations.
- A single naming function in the task produces both the declared
  @OutputFiles and the copied file names. Its inputs are the ABIs of the
  variant's outputs (what AGP builds), the flavor, and the build mode.
  The task fails if AGP built an APK for an ABI that was not declared.
- Per-ABI versionCode for --split-per-abi is a read-then-set on
  VariantOutput.versionCode in onVariants. This is a documented
  exception to the no-configuration-time-get rule, because the lazy
  form is circular. The read relies on AGP's compatibility mode
  (android.compatibility.enableLegacyApi, on by default through 9.x).
- The applicationVariants.configureEach block and its
  @Suppress("DEPRECATION") markers are deleted. The add-to-app module
  path keeps using libraryVariants until the next phase.

Behavior change: Flutter's onVariants callback runs before an app's own
androidComponents.onVariants block, while ApkVariantOutput's
versionCodeOverride was applied after it. Apps that transform
output.versionCode there transform Flutter's offset value (for example
arm64-v8a, build 42, x10000: 422000 -> 20420000). The migration doc and
the breaking-change page draft describe this and give a recipe that
keeps the 422000-style numbers. No divergence warning is emitted,
because it would fire on the onVariants pattern that AGP recommends.

Part of flutter#166550.
@reidbaker
reidbaker force-pushed the agp-apk-copy-versioncode branch from 79ef973 to e15544b Compare October 2, 2026 01:38
@reidbaker reidbaker added the CICD Run CI/CD label Oct 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewers. This document feels AI generated but I have left it alone since it is not code and might help you review (things like the table for version codes).

* [outputApks] instead. Those and the files the copy writes are both named by [apkFileName] from
* the same inputs, so the declared outputs cannot drift from what the task produces.
*/
@DisableCachingByDefault(because = "Copying local APKs is faster than unpacking cache archives")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do I know this is true?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can't. It was an unmeasured claim, so I replaced it. The annotation now reads
@DisableCachingByDefault(because = "Not worth caching"). That is the same annotation and reason
Gradle puts on its own Copy and Sync tasks (checked with javap -v on
gradle-core-8.9.jar: org.gradle.api.tasks.Copy and Sync both carry
DisableCachingByDefault(because="Not worth caching")). The KDoc says that and makes no
performance claim.

Comment on lines +93 to +96
"Flutter could not read the APK metadata in $apkDir, so it cannot copy the " +
"APKs to ${destinationDir.get()}. Please file an issue at " +
"https://github.com/flutter/flutter/issues."
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are some reasons that we would fail to be able to load the apk. I can think of 1) when our name building produces a name that does not match gradle 2) possibly a file permission issue. Anything else?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BuiltArtifactsLoader.load(dir) returns null in exactly one case: dir/output-metadata.json
does not exist (AGP BuiltArtifactsLoaderImpl.loadFromFile: if (inputFile == null || !inputFile.exists()) return null).

  • Name building: not involved. load runs before any name is computed, and the names come
    from task inputs, not from files on disk.
  • File permissions: an unreadable file makes readText() throw an IOException, and
    malformed JSON throws too. Neither returns null.
  • Practical cause: AGP's packaging task always writes the metadata file. A transform that
    uses toTransformMany(SingleArtifact.APK) with ArtifactTransformationRequest gets it written
    by AGP ("this object will abstract away having to deal with [BuiltArtifacts] and manually load
    and write the metadata files", ArtifactTransformationRequest KDoc). So null means another
    plugin or build script replaced the artifact with plain toTransform and did not call
    BuiltArtifacts.save().

I put this in a comment above the call. The error says "no output-metadata.json", names
SingleArtifact.APK, and asks the user to contact the maintainer of the plugin that transforms
it. It does not ask them to file a Flutter issue. CopyFlutterApksTaskTest asserts both of those.

Comment on lines +101 to +103
// means AGP built an APK that Flutter did not declare as an output. Fail rather than
// write a file that Gradle does not track.
if (abi !in declaredAbis) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would these get mismatched?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With AGP alone they can't. AGP builds one APK per variant output, and the declared ABIs come from
those same outputs (a disabled output only makes the built set smaller, and that passes). The
check can fail only if another plugin or build script transforms SingleArtifact.APK into APKs
with different ABI filters. I kept it, because without it such a build writes an untracked file
into flutter-apk and up-to-date checks go wrong silently. The behavior is documented in three
places:

  • The CopyFlutterApksTask class KDoc.
  • The error message. It names SingleArtifact.APK and points at the plugin that transforms it,
    not at Flutter.
  • website-page-draft.md, which says what a transform must keep.

I also looked for plugins Flutter developers use that touch the APK:

Plugin What it does to the APK Trips the check?
Walle (Meituan), used by Flutter devs through pub package_by_walle and in #28701 writes extra channel APKs after assemble (legacy variant.outputs) No
VasDolly (Tencent) reads SingleArtifact.APK with BuiltArtifactsLoader, writes to its own dir No
packer-ng (archived) writes extra APKs after assemble No
AndResGuard (Tencent), Redex overwrite the APK in place after assemble No (metadata and filters unchanged)
Sentry, Firebase App Distribution, Gradle Play Publisher read SingleArtifact.APK only No
toTransformMany(SingleArtifact.APK) + ArtifactTransformationRequest (10 sampled build scripts, mostly renaming or signing) AGP writes the metadata No

I found no public plugin that uses plain toTransform(SingleArtifact.APK) without writing the
metadata, or that changes ABI filters. DexGuard, Crashlytics, AGConnect and the 360/Legu
hardening tools are closed source, so I couldn't verify them.

* `packages/flutter_tools/lib/src/android/gradle.dart`, which is how the Flutter tool
* finds these files.
*/
internal fun apkFileName(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the only place we build apk files names based on gradle parameters?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the Gradle plugin, yes. This PR deletes the other one, the filename += block in the
applicationVariants callback. The Flutter tool rebuilds the names on the reading side in three
places:

Where Used for Name format
CopyFlutterApksTask.apkFileName (FGP) writes flutter-apk app[-abi][-flavor]-<mode>.apk
listApkPaths, lib/src/android/gradle.dart:1278 flutter build apk for app projects same
AndroidApk.fromAndroidProject, lib/src/android/application_package.dart:118-125 flutter run/install (no ABI) app[-flavor]-<mode>.apk
_apkFilesFor, lib/src/android/gradle.dart:149 add-to-app host (host/outputs/apk) app[-flavor]-<abi>-<mode>.apk, which is AGP's own naming, not FGP's

The FGP and tool sides are kept in sync only by tests on each side: CopyFlutterApksTaskTest,
the tool's gradle_test.dart, and integration tests that read flutter-apk. Nothing ties the two
together. Following the ratchet principle, I'm raising this here rather than changing it in this
PR.

Comment on lines +738 to +739
* seeds `output.versionCode` (with -1 when no versionCode is declared anywhere, as
* `BaseVariant.getVersionCode` did), so the check for an unset value is only a guard.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Temporal words describing previous behavior I thought were banned by the style guide?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 33f3987. The KDoc describes only the current behavior. The history (versionCodeOverride
ordering, manifest fallback) is in item 3, "Per-ABI versionCode", under "Features that must break" in
the migration doc.

* `ApkVariantOutput.versionCodeOverride`, which this replaces, was applied after such
* blocks, so for those apps the resulting versionCode differs from earlier Flutter
* releases. See "Features that must break" in
* docs/platforms/android/Migrating-Flutter-Gradle-Plugin-to-AGP-public-API.md.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer linking to the website documentation if possible.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The breaking-change page isn't published yet. Its source is
docs/platforms/android/website-page-draft.md, so the KDoc points at that section ("Setting
per-ABI or per-variant versionCode") and has a TODO(reidbaker) to switch to the docs.flutter.dev
URL, tracked in #193713. The migration doc links the
same issue.

Comment on lines +767 to +768
// The read relies on AGP's compatibility mode (`android.compatibility.enableLegacyApi`,
// on by default through AGP 9.x). With it off, AGP disallows unsafe reads of this

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably bad given that the goal is to no longer depend on the legacy api.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, and fixed. The plugin no longer reads output.versionCode:

  • DslVersionCodes snapshots defaultConfig.versionCode and each product flavor's
    versionCode in androidComponents.finalizeDsl.
  • forVariant(variant.productFlavors) resolves the base the way AGP merges it: the first flavor
    in dimension order that sets one, then defaultConfig.
  • onVariants only calls output.versionCode.set(...).

Evidence:

  • -Pandroid.compatibility.enableLegacyApi=false on AGP 9.3.1 builds and gives 1042/2042/4042.
  • Re-adding the .orNull read fails with "Cannot query the value of this property because
    configuration of project ':app' has not completed yet".
  • A strict-mode case was added to flutter_build_apk_split_per_abi_test.
  • The unit-test Property mock stubs only set, so any read fails the test.

Trade-offs, now documented:

  • A versionCode declared only in the manifest is not offset. Flutter logs a warning in that case.
  • A finalizeDsl callback that runs after Flutter's and changes versionCode is not seen.


// Check that `flutter build apk --split-per-abi` generates a versionCode equal to abiIndex * 1000 + buildNumber
// Check that `flutter build apk --split-per-abi` generates a versionCode equal to
// abiIndex * 1000 + buildNumber (then multiplied by 10000 when [usingCustomAppGradleFile]), and

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why multiplied by 10000?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It comes from #174081 (gmackall), the regression test for #173917. In that issue an F-Droid user
needs versionCode * 10000 + abiCode * 1000 and set it with
output.versionCode.set(output.versionCode.get() * 10000 ...) in their own onVariants. The
fixture copies that transform. Its abiCodes map is left over from the issue snippet and is
only used as a non-null guard. The test checks that an app's onVariants change to versionCode
survives Flutter's per-ABI offset.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any way to keep the this behavior while migrating to the new api?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not without depending on the compatibility mode. The legacy result
(offset * 1000 + <the value the app's onVariants set>) needs Flutter to run after the app's
callback and read the value the app set. That read is exactly what AGP rejects when
enableLegacyApi=false. Gradle Property has no lazy "transform my current value" (the .map
form is circular), so no lazy workaround exists.

  • Possible but not recommended: register Flutter's onVariants from inside its
    finalizeDsl, which runs after the app's script has registered its callbacks, and read the
    app's value only when compatibility mode is on. I haven't tested this. It keeps the old
    numbers only on AGP 9 with the default setting. In strict mode Flutter would then run last and
    overwrite absolute values set by the app, which is worse.
  • Why the order in this PR fits strict mode: with compatibility mode off, apps can't read
    output.versionCode either, so they have to set absolute values. Flutter running first and the
    app's value winning is what lets that work.
  • Covering [Android] Flutter 3.35.1 cannot customize the version code during compilation. #173917's user: -Pforce-version-code-ignoring-abi=true (Implement version code overrides for split ABIs #187742) plus
    output.versionCode.set(flutter.versionCode * 10000 + abiCode * 1000) gives exactly their
    format, in both modes.
  • Docs: the migration doc and the website draft give the recipe and the before/after table.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gmackall I rejected the idea of using code that kept the old behavior when we could get the older apis and only do the version breaking on new AGP versions. I just thought that would be confusing for users.

If AGP 10 ends up allowing the opt out to keep working I am going to be sad that we didnt build a way to keep the old behavior. What do you think?

…ures

- Take the per-ABI versionCode base from a finalizeDsl snapshot of the
  DSL versionCodes (DslVersionCodes) instead of reading
  output.versionCode, which AGP rejects during configuration when
  android.compatibility.enableLegacyApi=false. Warn and skip when the
  DSL declares no versionCode.
- Add a strict-mode (enableLegacyApi=false) case to
  flutter_build_apk_split_per_abi_test.
- CopyFlutterApksTask: use Gradle's Copy/Sync caching annotation and
  reason; explain when the metadata can be missing and when the ABI
  check can fail; point both errors at the plugin that transforms
  SingleArtifact.APK instead of asking for a Flutter issue.
- Document what an APK transform must keep in website-page-draft.md.
- Link the website-page-draft.md references to tracking issue flutter#193713.
- Remove temporal wording from KDoc and docs.
@flutter-dashboard flutter-dashboard Bot removed the CICD Run CI/CD label Oct 2, 2026

@reidbaker reidbaker left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

meta comment. This pr seems to add quite verbose code comments. I have not reviewed all of them but make sure the code comments add value to future maintainers and are easy to understand or make the code easier to understand.

* at Flutter. Plugins that only read the APK artifact, or that write extra APKs after `assemble`
* (as channel-packaging plugins do), do not cause either failure.
*/
@DisableCachingByDefault(because = "Not worth caching")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reason is much worse than the reason you gave previously.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. "Not worth caching" said nothing. The reason now states two facts you can check: the inputs are APKs already on local disk, and a build-cache entry for this task would store those same bytes again (Gradle caches a task's outputs, and the outputs here are the copied APKs). It makes no speed claim. Fixed in f668780.

// or build script transforms the APK artifact into APKs with different ABI filters.
// Fail rather than write a file that Gradle does not track.
if (abi !in declaredAbis) {
throw GradleException(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicate error string? Consider pulling out into a variable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in f668780. Both errors use transformedApkError(problem, fix), so the shared guidance is written once.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gmackall I rejected the idea of using code that kept the old behavior when we could get the older apis and only do the version breaking on new AGP versions. I just thought that would be confusing for users.

If AGP 10 ends up allowing the opt out to keep working I am going to be sad that we didnt build a way to keep the old behavior. What do you think?

…er comments

- CopyFlutterApksTask: state why caching is disabled (local copy; a cache
  entry would duplicate the APKs) and build both errors through one
  transformedApkError helper.
- Trim comments and KDoc added by this PR to what the code does not
  already say.
@reidbaker-agent

Copy link
Copy Markdown
Contributor Author

Re the review note on verbose comments: thanks. I went through every comment this PR adds and kept only what a maintainer can't get from the code: why the snapshot exists (strict mode), the one case it misses (later finalizeDsl), why the assemble task is matched by name, and where the file-name contract lives. Net −105 comment lines in f668780. The migration doc and website draft were not changed in this pass.

@reidbaker reidbaker added the CICD Run CI/CD label Oct 2, 2026
@reidbaker
reidbaker marked this pull request as ready for review October 2, 2026 19:53
@reidbaker
reidbaker requested a review from a team as a code owner October 2, 2026 19:53
@reidbaker
reidbaker requested review from camsim99, gmackall and mboetger and removed request for a team October 2, 2026 19:53

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request migrates the Flutter Gradle Plugin's APK copying and per-ABI versionCode configuration to the public Android Gradle Plugin (AGP) variant APIs. It introduces a new CopyFlutterApksTask to copy and rename APKs using SingleArtifact.APK and BuiltArtifactsLoader, replacing the previous doLast copy block. It also adds DslVersionCodes to capture version codes in finalizeDsl and apply per-ABI offsets in onVariants, enabling split-per-abi builds to function when AGP's compatibility mode is disabled. Corresponding unit and integration tests, as well as migration documentation, are updated. Review feedback suggests using tasks.matching instead of tasks.configureEach when configuring the assemble task dependency to avoid configuration performance overhead.

Comment thread packages/flutter_tools/gradle/src/main/kotlin/FlutterPlugin.kt
return
}
if (baseVersionCode == null) {
project.logger.warn(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be an error or a warn? The default

} else {
options.add('-q');
}
this will not be shown to the user. Is that ok?

// plugin instance that owns the resolved Flutter SDK and local engine paths.
val flutterGradlePlugin = this
val isApplicationProject = FlutterPluginUtils.isFlutterAppProject(projectToAddTasksTo)
// Set in finalizeDsl, which AGP runs before any onVariants callback.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not call this in the onVariants callback then?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CICD Run CI/CD d: docs/ flutter/flutter/docs, for contributors platform-android Android applications specifically tool Affects the "flutter" command-line tool. See also t: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants