Play Console Recommendations¶
Google Play's console raises automated "recommendations" against the uploaded bundle. They are produced by static analysis of the shipped bytecode, so they fire on code paths that exist in a dependency even when the app can never reach them. This page records the ones we have already investigated, so nobody has to work through the same deobfuscation twice.
Raw recommendation text is archived alongside the crash reports in
dev/crashes/<version>/recommendations.txt.
Bitmap downsampling — BitmapFactory without BitmapFactory.Options (v1.0.2)¶
Your app uses BitmapFactory without downsampling at:
f3.j.b
Status: not actionable. Unreachable in this app.
Deobfuscated against release/V1.0.2/mapping.txt:
com.mr.flutter.plugin.filepicker.FileUtils -> f3.j
android.net.Uri compressImage(android.net.Uri, int, android.content.Context) -> b
So the finding is FileUtils.compressImage() in the file_picker plugin, not
our code. In the plugin it is guarded:
private fun processUri(activity: Activity, uri: Uri, compressionQuality: Int): Uri {
return if (compressionQuality > 0 && isImage(activity.applicationContext, uri)) {
compressImage(uri, compressionQuality, activity.applicationContext)
} else {
uri
}
}
compressionQuality defaults to 0 in file_picker's Dart API, and both of our
call sites leave it at the default while restricting the picker to non-image
types:
lib/features/file_analysis/file_analysis_screen.dart— audio extensionslib/features/survey/survey_setup_screen.dart—txt/csv
Both conditions therefore fail and compressImage never runs. R8 keeps the
method because it is reachable from the plugin's method-channel dispatch, which
is why the analyzer still sees it.
If this ever changes: the moment we pass a non-zero compressionQuality or
allow image types through the picker, this stops being theoretical and the fix
belongs upstream in file_picker (it should set BitmapFactory.Options.inSampleSize).
R8 — optimized resource shrinking (v1.0.2)¶
Optimized resource shrinking is not enabled. Update your Android Gradle Plugin to version 9.0 or higher.
Status: fixed (AGP 9 upgrade), pending device testing.
The recommendation is correct and cannot be worked around by adding a flag. The
property that controls it is android.r8.optimizedResourceShrinking, and it
exists only in AGP 9.x. Verified by scanning the plugin jars directly:
| Property | AGP 8.11.1 | AGP 9.0.1 |
|---|---|---|
android.r8.optimizedShrinking |
yes | yes |
android.r8.integratedResourceShrinking |
yes | yes |
android.r8.optimizedResourceShrinking |
no | yes |
The app already sets minifyEnabled true and shrinkResources true; this is a
further R8 mode, not a missing basic setting.
What the upgrade involved¶
AGP 9 pulls two other tools with it. These are the versions now in the repo:
| Tool | Was | Now |
|---|---|---|
| Android Gradle Plugin | 8.11.1 | 9.0.1 |
| Gradle | 8.14 | 9.1.0 |
| Kotlin Gradle Plugin | 2.2.20 | 2.3.20 |
Gradle 9.1.0 is the floor, not 9.0.0 — AGP 9.0.1 rejects 9.0.0 outright:
Failed to apply plugin 'com.android.internal.version-check'.
> Minimum supported Gradle version is 9.1.0. Current version is 9.0.0.
Result: flutter build appbundle --release succeeds, and the bundle went from
230.7 MB to 229.5 MB, which is the optimized resource shrinking doing its
job (the bulk of the bundle is ONNX models, which are noCompress, so a 1.2 MB
drop comes entirely out of the much smaller resource section).
Notes on our specific configuration:
- Proguard file is already correct. AGP 9 rejects
getDefaultProguardFile('proguard-android.txt')because it implies-dontoptimize. We already useproguard-android-optimize.txt, which is the replacement AGP 9 asks for. android.newDsl=falseinandroid/gradle.propertiesopts out of the new DSL, which becomes the default in AGP 9 and is the only option in AGP 10. It is still honoured in 9.x, so it does not block the upgrade, but it is borrowed time.com.android.asset-packmust move in lockstep with AGP — we build amodels_packasset pack.- Flutter 3.44 supports AGP up to 9.1 and its own project template already defaults to AGP 9.0.1 + Gradle 9.1.0, so this is a well-trodden path rather than bleeding edge.
Built-in Kotlin (migrated 2026-10-11)¶
The AGP 9 upgrade surfaced a second warning:
WARNING: Your app uses the following plugins that apply Kotlin Gradle Plugin (KGP):
dynamic_color, flutter_foreground_task, flutter_onnxruntime, flutter_tts
Future versions of Flutter will fail to build if your app uses plugins that apply KGP.
Flutter's android.builtInKotlin=false is a temporary compatibility mode: the
Flutter Gradle plugin applies KGP to every Android module that does not declare
it, and Flutter has announced the mode will be removed. Each named plugin
needed its own resolution:
| Plugin | Resolution |
|---|---|
flutter_tts |
replaced by the in-house com.birdnet/tts channel |
dynamic_color |
1.9.0 dropped KGP |
flutter_foreground_task |
11.0.0 dropped KGP; we ship 11.0.3 |
flutter_onnxruntime |
1.8.5 applies KGP only when built-in Kotlin is off; we ship 1.9.0 (see ONNX Runtime Pin) |
With all four resolved, android/app/build.gradle no longer declares
kotlin-android or kotlinOptions; the jvmTarget moved into a top-level
kotlin { compilerOptions { … } } block, which both KGP and AGP's built-in
Kotlin register. android.builtInKotlin=true in gradle.properties lets AGP
compile Kotlin itself (Flutter 3.47+ required). org.jetbrains.kotlin.android
stays declared in settings.gradle: Flutter's template still declares it, and
the compatibility mode needs it on the classpath if it ever has to be
re-enabled.
One warning line remains and is a false positive:
WARNING: Your app uses the following plugins that apply Kotlin Gradle Plugin (KGP): flutter_onnxruntime
Flutter detects KGP by regex-scanning each plugin's build.gradle for the
literal apply plugin: "kotlin-android" without evaluating the if around it.
flutter_onnxruntime keeps that literal deliberately: when the text is absent,
Flutter applies KGP to the module itself whenever built-in Kotlin is off, which
is exactly the fatal AGP 9 error the guard prevents
(masicai/flutter_onnxruntime#76). Flutter closed the false-positive report
(flutter/flutter#189770, 2026-10-02) saying the regex will be patched in a
future release. Until that lands, or the plugin drops the guard in favor of the
unconditional migration Flutter recommends, the line is expected and nothing in
this repository can remove it.
Ship it separately from a crash hotfix¶
The build is green, but a toolchain bump changes how every class is compiled and
shrunk, and optimized resource shrinking rewrites resources — breakage there
shows up at runtime, not at build time. A successful bundleRelease is not
evidence the app still works.
Do not fold this into a hotfix release. Land it on its own, and smoke-test on real devices across a couple of Android versions first — with attention to anything resource-driven (icons, notification channels, widget layouts, the Quick Listen home-screen widget). Bundling an unvalidated toolchain change into a crash fix is exactly the failure mode that produced the v1.0.2 crash.