Skip to content

[in_app_purchase_android] Guard async billing callbacks against duplicate listener responses - #12640

Merged
auto-submit[bot] merged 3 commits into
flutter:mainfrom
eszter-varga:in-app-purchase-android-reply-once
Oct 13, 2026
Merged

auto-submit[bot] merged 3 commits into
flutter:mainfrom
eszter-varga:in-app-purchase-android-reply-once

Conversation

@eszter-varga

@eszter-varga eszter-varga commented Aug 26, 2026 •

Copy link
Copy Markdown
Contributor

The Play Billing library can invoke the response listener of a single async
call more than once (observed in production via its internal watchdog/timeout
path, zzaf.run → zzan.run, with the real response also arriving). A pigeon
reply may only be submitted once; the second completion throws
IllegalStateException: Reply already submitted on the main thread and kills
the process.

startConnection already guards against a double onBillingSetupFinished
with its alreadyFinished flag; this PR extends the same protection to the
other 9 async methods via a replyOnce(methodName, callback) wrapper — an
AtomicBoolean.getAndSet(true) single-shot around the pigeon callback. The
first completion wins; duplicates are dropped with a Log.d. Call sites bind
the pigeon parameter as rawCallback and rebind callback to the wrapper, so
the method bodies are otherwise unchanged and every completion path
(null-client error, null-activity error, listener response, RuntimeException
catch) is single-shot. Listener side effects (updateCachedProducts) still
run on dropped duplicates, so if an error response lands first and the real
one second, the product cache still receives real data for a later
launchBillingFlow.

Two JUnit regression tests fire a captured listener twice with distinct
results and assert exactly one pigeon completion holding the first result.

Developed with AI assistance; tested and verified by the submitter.

Fixes flutter/flutter#191832
Related: flutter/flutter#103432, flutter/flutter#134967

Pre-Review Checklist

Note: this PR and #12639 both bump the package to 0.5.3; whichever lands
second will be rebased and re-bumped.

Footnotes

  1. Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. ↩ ↩2

@google-cla

google-cla Bot commented Aug 26, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@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 introduces a replyOnce helper method in MethodCallHandlerImpl.java to prevent crashes caused by duplicate listener responses from the Play Billing library. The helper wraps asynchronous callbacks using an AtomicBoolean to ensure they are executed only once, dropping subsequent duplicate invocations. Corresponding regression tests have been added to MethodCallHandlerTest.java to verify this behavior, and the package version has been updated to 0.5.3 in pubspec.yaml and CHANGELOG.md. There are no review comments, so no additional feedback is provided.

…cate listener responses

The Play Billing library can invoke the response listener of a single async
call more than once (internal watchdog/timeout path); the second pigeon
completion threw IllegalStateException 'Reply already submitted' on the main
thread and killed the process. startConnection already had its own
alreadyFinished guard; this extends the same single-shot protection to the
other 9 async methods via a replyOnce AtomicBoolean wrapper.

Claude-Session: https://claude.ai/code/session_012juAFafaPDBSvR7pcqTLMd
@eszter-varga
eszter-varga force-pushed the in-app-purchase-android-reply-once branch from d8aaa08 to 2d177ab Compare August 28, 2026 12:59
@stuartmorgan-g stuartmorgan-g added the triage-android Should be looked at in Android triage label Sep 1, 2026
@jesswrd jesswrd added the CICD Run CI/CD label Sep 1, 2026
@elliette

Copy link
Copy Markdown
Member

[from triage] gentle ping - @gmackall are you still reviewing this PR? Thanks!

@gorkemdurgun

Copy link
Copy Markdown

We're hitting this in production as well (in_app_purchase_android 0.5.0 / Play Billing 8.0.0), on queryProductDetailsAsync, through a different internal path than the one in the description (zzN → zzap.run rather than zzaf.run → zzan.run):

java.lang.IllegalStateException: Reply already submitted
  at io.flutter.embedding.engine.dart.DartMessenger$Reply.reply (DartMessenger.java:431)
  at io.flutter.plugin.common.BasicMessageChannel$IncomingMessageHandler$1.reply (BasicMessageChannel.java:266)
  at io.flutter.plugins.inapppurchase.InAppPurchaseApi$Companion.setUp$lambda$24$lambda$23$lambda$22 (InAppPurchaseApi.java:1991)
  at io.flutter.plugins.inapppurchase.ResultCompat.success (ResultUtils.kt:17)
  at io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.lambda$queryProductDetailsAsync$4 (MethodCallHandlerImpl.java:234)
  at com.android.billingclient.api.BillingClientImpl.zzN (BillingClientImpl.java:4)
  at com.android.billingclient.api.zzap.run (zzap.java:71)

Seen on Honor LLY-NX1 (Android 16), with the app resuming from background.

I also ran the Android unit tests on this branch (2d177ab) with Flutter 3.47.5:

  • MethodCallHandlerTest: all 46 pass, including the two new *_duplicateListenerResponseIsIgnored tests.
  • With the MethodCallHandlerImpl.java change reverted, both new tests fail, so they do cover the regression.

Would be great to see this land. Thanks @eszter-varga!

# Conflicts:
#	packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md
@flutter-dashboard flutter-dashboard Bot removed the CICD Run CI/CD label Sep 29, 2026
@gmackall gmackall added the CICD Run CI/CD label Sep 29, 2026
@gmackall
gmackall requested review from a team and camsim99 and removed request for a team September 29, 2026 18:51
@alonaviram

Copy link
Copy Markdown

We hit this in production too, on in_app_purchase_android 0.5.2 with Play Billing 9.1.0. Over the last 90 days we logged 240 crashes from 235 installs. 93% happened while the app was in the background, mostly on Android 15/16 (Vivo, Oppo, Xiaomi and Realme).

In every 9.1.0 event, the crashing (second) reply comes from Billing's watchdog:

MethodCallHandlerImpl.lambda$queryProductDetailsAsync$5
  ← BillingClientImpl.zzaa ← zzbi.run ← zzbb.run ← Handler (main)

Here is what the 9.1.0 bytecode shows. queryProductDetailsAsync submits the query to the billing executor. It also posts a watchdog to the main handler at 0.95 × 30 s = 28.5 s. If the Future isn't done by then, the watchdog calls the listener with "Timeout communicating with service.". The worker calls the same listener from inside the Future, before the Future completes. So if the real response lands close to the 28.5 s mark, both paths reply. That fits our data: no crash happened sooner than 31 s after session start.

The replyOnce guard in this PR covers that path. Could it be rebased onto main to clear the conflicts so it can land? Thanks!

@camsim99 camsim99 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.

LGTM!

@flutter-dashboard flutter-dashboard Bot removed the CICD Run CI/CD label Oct 6, 2026
@camsim99 camsim99 added the CICD Run CI/CD label Oct 6, 2026
@camsim99 camsim99 added the autosubmit Merge PR when tree becomes green via auto submit App label Oct 13, 2026
@auto-submit
auto-submit Bot merged commit c79bba3 into flutter:main Oct 13, 2026
14 checks passed
opensource-fork-sync Bot pushed a commit to cla7aye15I4nd/opensource-flutter__flutter that referenced this pull request Oct 15, 2026
Roll Packages from a1ed23c to da40eaf (33 revisions)

flutter/packages@a1ed23c...da40eaf

2026-10-15 tarrinneal@gmail.com [pigeon]add support for sealed classes
in native interop (flutter/packages#13175)
2026-10-15 tarrinneal@gmail.com [pigeon] Replace full JNI bindings with
stubs for API classes that aren't needed (flutter/packages#13173)
2026-10-14 ksanaullah383.khan@gmail.com [material_ui] Fix
self-comparison and assert bugs in semantics_tester
(flutter/packages#12722)
2026-10-14 magder@google.com [camera_avfoundation] Replace the
deprecated 'videoOrientation' with 'videoRotationAngle' on iOS 17+
(flutter/packages#13247)
2026-10-14 zengyan@88.com [material_ui] Fix GridTile crash when the
incoming constraints are unbounded (flutter/packages#12586)
2026-10-14 magder@google.com [in_app_purchase_storekit] Remove unused
and deprecated "SKDownload" hosted content (flutter/packages#13222)
2026-10-14 55765052+MohanadAbdallah-mv@users.noreply.github.com
[material_ui] Add supportingTextPadding to InputDecorator
(flutter/packages#12769)
2026-10-14 37883572+edwin-hollen@users.noreply.github.com
[vector_graphics_codec] Add support for encoding and decoding paint blur
commands (flutter/packages#13148)
2026-10-14 ketulm8@gmail.com [go_router] Fix redirect zone async error
handling (flutter/packages#12836)
2026-10-14 fluttergithubbot@gmail.com Sync release-go_router-18.1.0 to
main (flutter/packages#13260)
2026-10-14 fluttergithubbot@gmail.com Sync release-cupertino_ui-1.1.3 to
main (flutter/packages#13258)
2026-10-14 lsaudon@gmail.com [material_ui] add enableInlinePrediction
parameter to TextFormField (flutter/packages#12793)
2026-10-14 fluttergithubbot@gmail.com Sync release-material_ui-1.7.0 to
main (flutter/packages#13259)
2026-10-14 sokolovskyi.konstantin@gmail.com [material_ui] Add the
Material shapes. (flutter/packages#12771)
2026-10-14 64028623+Ahtsham0715@users.noreply.github.com [material_ui]
Add tertiary tap callbacks to InkResponse and InkWell
(flutter/packages#12676)
2026-10-14 engine-flutter-autoroll@skia.org Roll Flutter from
4249ccb to 426be96 (44 revisions) (flutter/packages#13256)
2026-10-14 47866232+chunhtai@users.noreply.github.com [material_ui]
fixes expanded state in the a11y_assessment home page dropdown
(flutter/packages#13219)
2026-10-14 zhongliu88889@gmail.com [VPAT] Fix semantics in
a11y_assessments drawer, radio, and rail demos (flutter/packages#13159)
2026-10-14 5684363+tenninebt@users.noreply.github.com
[google_maps_flutter] Add onPointOfInterestTap (flutter/packages#11872)
2026-10-14 Gornivv@gmail.com [go_router] Fix shell route key collisions
and dynamic branch updates (flutter/packages#12549)
2026-10-14 engine-flutter-autoroll@skia.org Manual roll Flutter from
d03768e to 4249ccb (158 revisions) (flutter/packages#13220)
2026-10-14 okorohelijah@google.com [image_picker_ios] Require Flutter
3.47 / iOS 15 and drop deprecated kUTType code paths
(flutter/packages#13224)
2026-10-13 louisehsu@google.com [in_app_purchase_storekit] Fixes
Storekit 2 flaky tests (flutter/packages#13213)
2026-10-13 stuartmorgan@google.com [local_auth] Convert Android to new
Pigeon suspend support (flutter/packages#12894)
2026-10-13 50643541+Mairramer@users.noreply.github.com [material_ui] Add
enabled parameter to DropdownButtonFormField (flutter/packages#12610)
2026-10-13 molchanovia.dev@gmail.com [go_router] Link ShellRoute docs to
StatefulShellRoute (flutter/packages#13120)
2026-10-13 jmccandless@google.com FadeForwards page transition
performance (flutter/packages#13161)
2026-10-13 101952451+eszter-varga@users.noreply.github.com
[in_app_purchase_android] Guard async billing callbacks against
duplicate listener responses (flutter/packages#12640)
2026-10-13 victor.orozco@cloudsufi.com [google_sign_in] Remove the
Objective-C exception catcher (flutter/packages#13156)
2026-10-13 magder@google.com [camera_avfoundation] Remove availability
checks below the package minimum iOS 13 (flutter/packages#13214)
2026-10-13 magder@google.com [google_maps_flutter_ios] Remove iOS 14
availability check from example UI tests (flutter/packages#13218)
2026-10-13 stuartmorgan@google.com [google_sign_in] Convert
implementation to Kotlin (flutter/packages#13005)
2026-10-13 victor.orozco@cloudsufi.com [image_picker_ios] PR 2/5 Add a
PHImageManager image-data seam and migrate plugin native tests to Swift
Testing (flutter/packages#13079)

If this roll has caused a breakage, revert this CL and set the roller
to dry run mode using the controls here:
https://autoroll.skia.org/r/flutter-packages-flutter-autoroll
Please CC flutter-ecosystem@google.com on the revert to ensure that a
human
is aware of the problem.

To file a bug in Flutter:
https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
...
suryaKommana2662 pushed a commit to suryaKommana2662/flutter that referenced this pull request Oct 29, 2026
Roll Packages from a1ed23c to da40eaf (33 revisions)

flutter/packages@a1ed23c...da40eaf

2026-10-15 tarrinneal@gmail.com [pigeon]add support for sealed classes
in native interop (flutter/packages#13175)
2026-10-15 tarrinneal@gmail.com [pigeon] Replace full JNI bindings with
stubs for API classes that aren't needed (flutter/packages#13173)
2026-10-14 ksanaullah383.khan@gmail.com [material_ui] Fix
self-comparison and assert bugs in semantics_tester
(flutter/packages#12722)
2026-10-14 magder@google.com [camera_avfoundation] Replace the
deprecated 'videoOrientation' with 'videoRotationAngle' on iOS 17+
(flutter/packages#13247)
2026-10-14 zengyan@88.com [material_ui] Fix GridTile crash when the
incoming constraints are unbounded (flutter/packages#12586)
2026-10-14 magder@google.com [in_app_purchase_storekit] Remove unused
and deprecated "SKDownload" hosted content (flutter/packages#13222)
2026-10-14 55765052+MohanadAbdallah-mv@users.noreply.github.com
[material_ui] Add supportingTextPadding to InputDecorator
(flutter/packages#12769)
2026-10-14 37883572+edwin-hollen@users.noreply.github.com
[vector_graphics_codec] Add support for encoding and decoding paint blur
commands (flutter/packages#13148)
2026-10-14 ketulm8@gmail.com [go_router] Fix redirect zone async error
handling (flutter/packages#12836)
2026-10-14 fluttergithubbot@gmail.com Sync release-go_router-18.1.0 to
main (flutter/packages#13260)
2026-10-14 fluttergithubbot@gmail.com Sync release-cupertino_ui-1.1.3 to
main (flutter/packages#13258)
2026-10-14 lsaudon@gmail.com [material_ui] add enableInlinePrediction
parameter to TextFormField (flutter/packages#12793)
2026-10-14 fluttergithubbot@gmail.com Sync release-material_ui-1.7.0 to
main (flutter/packages#13259)
2026-10-14 sokolovskyi.konstantin@gmail.com [material_ui] Add the
Material shapes. (flutter/packages#12771)
2026-10-14 64028623+Ahtsham0715@users.noreply.github.com [material_ui]
Add tertiary tap callbacks to InkResponse and InkWell
(flutter/packages#12676)
2026-10-14 engine-flutter-autoroll@skia.org Roll Flutter from
4249ccb to 426be96 (44 revisions) (flutter/packages#13256)
2026-10-14 47866232+chunhtai@users.noreply.github.com [material_ui]
fixes expanded state in the a11y_assessment home page dropdown
(flutter/packages#13219)
2026-10-14 zhongliu88889@gmail.com [VPAT] Fix semantics in
a11y_assessments drawer, radio, and rail demos (flutter/packages#13159)
2026-10-14 5684363+tenninebt@users.noreply.github.com
[google_maps_flutter] Add onPointOfInterestTap (flutter/packages#11872)
2026-10-14 Gornivv@gmail.com [go_router] Fix shell route key collisions
and dynamic branch updates (flutter/packages#12549)
2026-10-14 engine-flutter-autoroll@skia.org Manual roll Flutter from
d03768e to 4249ccb (158 revisions) (flutter/packages#13220)
2026-10-14 okorohelijah@google.com [image_picker_ios] Require Flutter
3.47 / iOS 15 and drop deprecated kUTType code paths
(flutter/packages#13224)
2026-10-13 louisehsu@google.com [in_app_purchase_storekit] Fixes
Storekit 2 flaky tests (flutter/packages#13213)
2026-10-13 stuartmorgan@google.com [local_auth] Convert Android to new
Pigeon suspend support (flutter/packages#12894)
2026-10-13 50643541+Mairramer@users.noreply.github.com [material_ui] Add
enabled parameter to DropdownButtonFormField (flutter/packages#12610)
2026-10-13 molchanovia.dev@gmail.com [go_router] Link ShellRoute docs to
StatefulShellRoute (flutter/packages#13120)
2026-10-13 jmccandless@google.com FadeForwards page transition
performance (flutter/packages#13161)
2026-10-13 101952451+eszter-varga@users.noreply.github.com
[in_app_purchase_android] Guard async billing callbacks against
duplicate listener responses (flutter/packages#12640)
2026-10-13 victor.orozco@cloudsufi.com [google_sign_in] Remove the
Objective-C exception catcher (flutter/packages#13156)
2026-10-13 magder@google.com [camera_avfoundation] Remove availability
checks below the package minimum iOS 13 (flutter/packages#13214)
2026-10-13 magder@google.com [google_maps_flutter_ios] Remove iOS 14
availability check from example UI tests (flutter/packages#13218)
2026-10-13 stuartmorgan@google.com [google_sign_in] Convert
implementation to Kotlin (flutter/packages#13005)
2026-10-13 victor.orozco@cloudsufi.com [image_picker_ios] PR 2/5 Add a
PHImageManager image-data seam and migrate plugin native tests to Swift
Testing (flutter/packages#13079)

If this roll has caused a breakage, revert this CL and set the roller
to dry run mode using the controls here:
https://autoroll.skia.org/r/flutter-packages-flutter-autoroll
Please CC flutter-ecosystem@google.com on the revert to ensure that a
human
is aware of the problem.

To file a bug in Flutter:
https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autosubmit Merge PR when tree becomes green via auto submit App CICD Run CI/CD p: in_app_purchase platform-android triage-android Should be looked at in Android triage

Projects

None yet

9 participants