Repository navigation
Description
Steps to reproduce
Re-filing of #188088, which was closed by the bot for missing repro info
while the triage question was unanswered. The requested information is
included up front this time; a PR with the fix and a regression test
accompanies this issue.
Hard to force deterministically — it needs the Play Billing service to flap
(e.g. Play Store self-updating, or a device in a bad state) right around the
moment the app is foregrounded, while BillingClientManager is connected.
- App is backgrounded; the Play Billing service binding is lost.
- App is foregrounded; something probes billing (e.g.
InAppPurchase.instance.isAvailable()/restorePurchases()). - The platform fires
onBillingServiceDisconnected. BillingClientManager._connectimmediately calls
BillingClient.startConnectionsynchronously from within that callback.
Answering the triage question from #188088 (“does it reproduce with the
latest stable Flutter and the latest in_app_purchase_android release?”):
- The defective code path is unchanged in the latest release (
0.5.2,
2026-07-20) and onmain— re-verified at code level on 2026-08-26:
_connect()still wrapsstartConnectioninFuture<void>.sync(...),
which never yields before the platform send, so the send still runs
synchronously on the disconnect upcall. There is no
Future.delayed/Timeranywhere inbilling_client_manager.dart. - The production crashes were captured by Sentry on
0.4.0+8(2026-06-10 and
2026-06-11, identical stacks). Because the trigger is a Play-service flap,
it cannot be reproduced on demand on a healthy device — but the crashing
code is byte-identical in0.5.2, so the version bump cannot have fixed it. - Related [in_app_purchase][android]
BillingClientconnection is not restored after it's lost #110663 is a different issue: that was about the connection not
being restored; this one is about how the reconnect is issued (a
synchronous, re-entrant platform send inside the JNI upcall).
Expected results
A transient disconnect leads to a (possibly failed) reconnect that surfaces as
a Dart PlatformException, never a process abort.
Actual results
The process aborts with an uncatchable SIGABRT.
Crash (abridged):
SIGABRT: Abort
... fml::KillProcess
... flutter::PlatformViewAndroidJNIImpl::FlutterViewHandlePlatformMessage
(platform_view_android_jni_impl.cc:1348) # FML_CHECK(CheckException(env))
...
InAppPurchaseApi.startConnection (messages.g.dart)
BillingClient.startConnection (billing_client_wrapper.dart)
BillingClientManager._connect (billing_client_manager.dart)
Future.sync (future.dart)
BillingClientManager._connect (billing_client_manager.dart)
HostBillingClientCallbackHandler.onBillingServiceDisconnected (billing_client_wrapper.dart)
InAppPurchaseCallbackApi.setUp.<…> (messages.g.dart)
PlatformDispatcher._dispatchPlatformMessage (platform_dispatcher.dart)
Analysis
onBillingServiceDisconnected is delivered as an inbound platform
message, handled on the merged UI/platform thread inside a JNI upcall.
_connect then sends startConnection — an outbound platform message —
synchronously from that same stack (Future.sync runs the closure with no
event-loop yield). When the billing service is flapping, the Java side has a
pending exception at the moment the engine runs
FML_CHECK(fml::jni::CheckException(env)) in
FlutterViewHandlePlatformMessage, so it aborts. This is uncatchable from
Dart (see #144954).
Proposed fix
Yield one macrotask before the platform send so it never runs synchronously on
the disconnect upcall:
_readyFuture = Future<void>.sync(() async {
await Future<void>.delayed(Duration.zero); // escape the JNI upcall
if (_isDisposed) { _isConnecting = false; return; }
await client.startConnection(...);
_isConnecting = false;
});A microtask is not sufficient (microtasks drain before control returns to
the engine); a zero-duration timer is. A PR implementing exactly this (plus a
regression test) is attached.
Related (separate): the same _connect reconnects with no cap, so a
sustained service flap storms bindService
(IllegalStateException: Too many bind requests(999+)) — also an uncatchable
abort. Worth bounding (Dart-side backoff, or enabling Billing 8's
enableAutoServiceReconnection() in BillingClientFactoryImpl), but
separable from the fix above.
Environment
in_app_purchase_android: crash captured on0.4.0+8; code-verified
unfixed on0.5.2(latest release) andmainas of 2026-08-26.- Flutter: 3.44.4 (stable), Dart 3.12.2
- Android: production fleet (the two Sentry-captured occurrences above, both
~22–23 s after foregrounding, with identical stacks).
flutter doctor -v
[✓] Flutter (Channel stable, 3.44.4, on Debian GNU/Linux 12 (bookworm) 6.1.0-37-amd64, locale en_US.UTF-8) [216ms]
• Flutter version 3.44.4 on channel stable at /home/elinorapricot/devtools/flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision ad70ec4617 (9 weeks ago), 2026-06-24 11:07:06 -0700
• Engine revision a10d8ac38d
• Dart version 3.12.2
• DevTools version 2.57.0
• Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android, enable-ios, cli-animations, enable-native-assets, enable-swift-package-manager, omit-legacy-version-file, enable-lldb-debugging, enable-uiscene-migration
[✓] Android toolchain - develop for Android devices (Android SDK version 36.1.0) [13.6s]
• Android SDK at /home/elinorapricot/Android/Sdk/
• Emulator version 36.5.11.0 (build_id 15261927) (CL:N/A)
• Platform android-36.1, build-tools 36.1.0
• Java binary at: /home/elinorapricot/Tools/android-studio/jbr/bin/java
This is the JDK bundled with the latest Android Studio installation on this machine.
To manually set the JDK path, use: `flutter config --jdk-dir="path/to/jdk"`.
• Java version OpenJDK Runtime Environment (build 17.0.10+0-17.0.10b1087.21-11609105)
• All Android licenses accepted.
[✓] Chrome - develop for the web [72ms]
• CHROME_EXECUTABLE = /usr/bin/chromium
[✓] Linux toolchain - develop for Linux desktop [1,879ms]
• Debian clang version 14.0.6
• cmake version 3.25.1
• ninja version 1.11.1
• pkg-config version 1.8.1
• GL_EXT_framebuffer_blit: no
• GL_EXT_texture_format_BGRA8888: no
[✓] Connected device (3 available) [1,932ms]
• sdk gphone64 x86 64 (mobile) • emulator-5554 • android-x64 • Android 16 (API 36) (emulator)
• Linux (desktop) • linux • linux-x64 • Debian GNU/Linux 12 (bookworm) 6.1.0-37-amd64
• Chrome (web) • chrome • web-javascript • Chromium 140.0.7339.127 built on Debian GNU/Linux 12 (bookworm)
[✓] Network resources [1,087ms]
• All expected network resources are available.
• No issues found!
Activity
suryaKommana2662 commentedon Aug 27, 2026
@eszter-varga Thanks for the detailed report and the additional reproduction information. I was not able to reproduce the crash locally, as the reported behavior depends on the Play Billing service disconnecting/flapping during the reconnect sequence. The provided crash stack, analysis, and linked PR provide useful information. Routing this to
team-androidfor further investigation.