Repository navigation
Description
Steps to reproduce
Steps 1-4 are deterministic on any machine and prove the defect is in every release build.
Step 5 is the device-dependent manifestation — the reason this reaches you as a crash report
rather than a local repro.
1. Any Flutter app on 3.47.0 or later (reproduced on 3.47.0 and 3.47.2), built for Android
with minSdk 24, compileSdk 36, AGP 9.1.0, R8 enabled (i.e. a stock `--release` build).
2. flutter build appbundle --release
3. Find the obfuscated name of AccessibilityBridge and its synthetic lambdas in
build/app/outputs/mapping/release/mapping.txt:
io.flutter.view.AccessibilityBridge -> io.flutter.view.a
io.flutter.view.AccessibilityBridge$$ExternalSyntheticLambda1 -> t2
4. Extract the DEX from the AAB and disassemble the constructor:
dexdump -d classes4.dex # the DEX containing io.flutter.view.a
Observe in AccessibilityBridge.<init>:
sget v0, Landroid/os/Build$VERSION;.SDK_INT:I
const/16 v1, #int 34
if-lt v0, v1, 0040
003a: new-instance v0, Lt2; <-- API-34 lambda, never executes below 34
0040: new-instance v0, Ll; <-- the no-op lambda
0045: iput-object v0, v2, Lio/flutter/view/a;.<field>:Lb3; <-- the type join
Lt2; declares Landroid/app/UiModeManager$ContrastChangeListener; (API 34) and has
onContrastChanged(F)V. No stub for that interface exists in any DEX in the bundle.
5. Run that build on a pre-34 device whose ART eagerly resolves the type join at 0045.
The app dies at launch. Not every pre-34 device does this - most ART builds tolerate the
unresolved join - which is why this presents as a small, persistent slice of users rather
than a universal failure.
Expected results
The app launches. A version-gated branch that never executes on the device should not prevent
io.flutter.view.AccessibilityBridge from loading, and AccessibilityBridge. should contain
no API-34 types in its verifiable body.
Actual results
The app cannot launch at all on an affected device. FlutterView.attachToFlutterEngine constructs
AccessibilityBridge unconditionally, so the process dies in Activity.performStart before a single
line of Dart runs. No accessibility service needs to be enabled.
Fatal Exception: java.lang.NoClassDefFoundError: Failed resolution of: Lt2;
at io.flutter.view.AccessibilityBridge.(AccessibilityBridge.java:428)
at io.flutter.view.AccessibilityBridge.(AccessibilityBridge.java:447)
at io.flutter.embedding.android.FlutterView.attachToFlutterEngine(FlutterView.java:1225)
at io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.onCreateView(...:437)
at io.flutter.embedding.android.FlutterFragment.onCreateView(FlutterFragment.java:1121)
at androidx.fragment.app.Fragment.performCreateView(Fragment.java:3115)
...
at android.app.Activity.performStart(Activity.java:8330)
at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3670)
Caused by java.lang.ClassNotFoundException: t2
Device is Android 12/13 by the frames (Looper.loopOnce:201 / loop:288,
ActivityThread.main:7872, and jdk.internal.misc.Unsafe, which libcore moved to in S).
Mechanism: ART resolves the synthetic while verifying the type join of the ternary's two
branches at the iput-object, even though the if-lt means the API-34 branch never executes. The
superinterface does not exist below API 34, so DexFile.defineClass fails - and because it
swallows the NoClassDefFoundError into suppressed and returns null, the loader surfaces it as
ClassNotFoundException.
Seen on four consecutive releases of a production app. Lt2; is only R8's name for the
synthetic and varies per build; an earlier release crashed as Lio/flutter/view/a; - same
failure, different name.
Code sample
The form asks for a
flutter create bugreproduction. There isn't a Dart-level one: the defect
is in the engine's Java and manifests only on specific ART builds. Say so explicitly and give the
engine source instead — that is what a triager needs here.
No Dart-level reproduction is possible: the defect is in the engine's Android embedding, and any
Flutter app built in release mode carries it. The offending code is:
// engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java:426-432
private final ContrastChangedListener highContrastObserver =
(Build.VERSION.SDK_INT >= API_LEVELS.API_34)
? (ContrastChangedListener & UiModeManager.ContrastChangeListener)
contrast -> setHighContrastFlag()
: contrast -> {
/* no-op */
};
The SDK_INT guard is correct, and registerHighContrastObserver (:718) is properly annotated
@RequiresApi(API_LEVELS.API_34). The bug is that the guarded TYPE appears inside <init>'s
verifiable body: the intersection cast
(ContrastChangedListener & UiModeManager.ContrastChangeListener)
forces D8's desugared lambda to implement the API-34 interface, and the ternary puts it in a
type join the verifier must check on every device.
PROPOSED FIX - the precedent is already in this file. AccessibilityBridge gained an Api31Impl
holder in #184630 (8ad7de9c41e) for the same class of bug, and its javadoc states exactly the
rule this initializer breaks:
/**
* Isolates API-31 field references so that ART's class verifier does not attempt to resolve
* them when loading {@link AccessibilityBridge} on older API levels. Without this separation,
* the verifier eagerly resolves {@link Configuration#fontWeightAdjustment} at class-load time,
* causing a {@link NoSuchFieldError} crash on Android 11 devices (observed on Pixel 4a and
* OnePlus 8 Pro). This mirrors the fix AndroidX Compose applied for the same crash (AOSP
* b/353988277).
*/
@RequiresApi(API_LEVELS.API_31)
private static class Api31Impl { ... }
That isolation was urgent enough to be cherry-picked into the 3.44 stable line. The high-contrast
lambda - added later, in #182263 (engine 8bd5cd5e919, 2026-04-29) - reintroduces precisely the
pattern the comment warns against, one API level up.
The fix is the same shape, an @RequiresApi(34) holder so no API-34 type reaches <init>:
@RequiresApi(API_LEVELS.API_34)
private static class Api34Impl {
@DoNotInline
static ContrastChangedListener newHighContrastObserver(AccessibilityBridge bridge) {
return (ContrastChangedListener & UiModeManager.ContrastChangeListener)
contrast -> bridge.setHighContrastFlag();
}
}
with the field initialised to the no-op lambda and assigned from Api34Impl behind the existing
SDK_INT >= 34 check. registerHighContrastObserver also casts the field back to
UiModeManager.ContrastChangeListener at :723, so that cast moves into the holder too - it is
already inside an @RequiresApi(34) method, so the change is mechanical.
AFFECTED VERSIONS: 3.47.0, 3.47.1, 3.47.2, 3.48.0-0.1/0.2/0.3.pre, and master (lines 426-432
verbatim). NOT 3.44.9, which predates #182263. Verified by tag: `Api34Impl` count is 0 in all of
the above; `highContrastObserver` count is 0 at 3.44.9 and 3 at every 3.47/3.48 tag.
Screenshots or Video
Screenshots / Video demonstration
[Upload media here]
Logs
Two local flutter build appbundle --release runs, one with the shipped R8 config and one with
-keep class io.flutter.view.** { *; } plus android.enableR8.fullMode=false, establish that
this is a verification failure and not a shrinker failure:
-
Shipped config, mapping.txt:
io.flutter.view.AccessibilityBridge$$ExternalSyntheticLambda1 -> t2
io.flutter.view.AccessibilityBridge -> io.flutter.view.a -
Keep-rule build removes nothing (usage.txt: zero AccessibilityBridge entries) and only renames
the synthetic to io.flutter.view.a - which is exactly the name an earlier release crashed with.
It changes the name in the crash message, not whether the class links.
Workarounds tried, all ineffective:
-keep class io.flutter.view.** { *; } No effect; the class was never removed.
android.enableR8.fullMode=false No effect; not a shrinking problem.
Upgrade Flutter No effect; Api34Impl absent from all stables + master.
Avoid constructing AccessibilityBridge Not possible; attachToFlutterEngine always builds it.
try/catch around the attach No seam in the embedding, and it leaves no UI.
Downgrade to 3.44.9 Works, but it is a three-minor rollback.
Note for whoever triages: compileSdk = 36, so UiModeManager$ContrastChangeListener WAS present in
the android.jar this compiled against - R8's API modeling had the information needed to outline
the synthetic and did not. That is being filed separately against AGP/R8, but the Flutter-side
holder fixes this regardless of what R8 does, and is the fix this file already uses one API level
down.
Flutter Doctor output
[√] Flutter (Channel stable, 3.47.2, on Microsoft Windows [Version 10.0.26200.9278], locale en-GB) [631ms]
• Flutter version 3.47.2 on channel stable at C:\Users\andre\Documents\Flutter\flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision d3b14c8 (8 days ago), 2026-08-26 16:07:51 -0700
• Engine revision a804b26
• Dart version 3.13.2
• DevTools version 2.60.0
[√] Windows Version (Windows 11 or higher, 25H2, 2009) [2.6s]
[√] Android toolchain - develop for Android devices (Android SDK version 36.0.0) [6.4s]
• Android SDK at C:\Users\andre\AppData\Local\Android\sdk
• Platform android-36, build-tools 36.0.0
• Java version OpenJDK Runtime Environment (build 21.0.6+-13368085-b895.109)
• All Android licenses accepted.
[√] Chrome - develop for the web [208ms]
[√] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.14.13 (August 2025)) [206ms]
[√] Connected device (3 available) [769ms]
[√] Network resources [639ms]
• No issues found!
Activity
mbcorona commentedon Sep 3, 2026
Hi @andreu-puy, thanks for the clear report and the repro steps.
Routing to the accessibility team for further investigation.
[android] Isolate API-34 high contrast listener in AccessibilityBridg…
github-actions commentedon Sep 25, 2026
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of
flutter doctor -vand a minimal reproduction of the issue.