Skip to content

2.7.22#437

Open
ianrumac wants to merge 28 commits into
mainfrom
develop
Open

2.7.22#437
ianrumac wants to merge 28 commits into
mainfrom
develop

Conversation

@ianrumac

Copy link
Copy Markdown
Collaborator

2.7.22

Enhancements

  • Always notify backend of purchased entitlements
  • Adds a 5 second timeout to resolving latest purchases, allowing non-play store purchases to resolve

Fixes

  • Fix a dead buy button when re-presenting a cached paywall in the same session after a purchase, for both register() and embedded paywalls (getPaywall/getPaywallView). Per-presentation transient state (loading spinner and presentation-prepared flag) is now reset on each new presentation, so it no longer leaks from a previous presentation that was stopped without a finishing teardown.

Checklist

  • All unit tests pass.
  • All UI tests pass.
  • Demo project builds and runs.
  • I added/updated tests or detailed why my change isn't tested.
  • I added an entry to the CHANGELOG.md for any breaking changes, enhancements, or bug fixes.
  • I have run ktlint in the main directory and fixed any issues.
  • I have updated the SDK documentation as well as the online docs.
  • I have reviewed the contributing guide

claude and others added 28 commits July 13, 2026 21:43
Since #431 stopped tearing down backgrounded paywalls on a non-finishing
onStop, per-presentation transient state was no longer reset. A purchase
launches Google Play's billing sheet (a separate activity), producing a
non-finishing onStop that leaves loadingState=LoadingPurchase and
presentationDidFinishPrepare=true. On the next presentation of the same
cached PaywallView, the stale spinner swallows the buy tap and
onViewCreated() early-returns without re-wiring the view.

Reset the transient state (purchase/manual spinner -> Ready and the
presentation-prepared flags) at the start of PaywallView.present(), which
runs only for a genuinely new presentation. This does not revert #431: the
don't-destroy-on-background behavior is untouched, resume-same-instance
(onResume -> onViewCreated) is unaffected, and the in-flight purchase
spinner is never cleared because present() is not called mid-purchase.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6NCb2BMXSXwAN1vCMFvyc
Add 2 emulator (Firebase Test Lab) androidTest cases covering the PR #434
fix: after a consumable purchase, re-presenting the same cached paywall used
to leave the buy button dead because a stale LoadingPurchase overlay carried
over. Both tests drive the full-screen register() path (the fixed path) from a
fragment-hosted screen and purchase via Superwall test mode (no Play billing on
CI):

- PaywallHostFragment: minimal fragment that calls register() on view-create,
  mirroring the customer's fragment-hosted setup.
- RepresentTests.test_represent_after_consumable_purchase_reinvokes_purchase:
  present -> buy -> drawer -> Purchased -> dismiss -> re-present -> tap buy
  again; asserts the purchase drawer re-appears (dead pre-fix).
- RepresentTests.test_represent_resets_loading_state: after re-present, asserts
  the public loadingState is not LoadingPurchase (public-API analog of the
  internal reset unit test).

Wire up androidx.fragment:fragment-testing (+ fragment-testing-manifest for the
FragmentScenario host activity) via the version catalog. Flow keys off the
CONSUMABLE_REBUY_PLACEMENT / BUY_BUTTON_TEXT constants so the placement and CTA
label can be adjusted server-side.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6NCb2BMXSXwAN1vCMFvyc
…tPaywallView re-present is covered; rewire UI test to embed

The dead-buy-button fix previously lived in PaywallView.present(), which only
runs on the full-screen register() path. The embedded getPaywall/getPaywallView
API never calls present(), so an embedded re-present returned the same cached
PaywallView with stale LoadingPurchase/presentationDidFinishPrepare and the bug
reproduced there. Both paths funnel through PaywallManager.getPaywallView's
cache-hit branch on a re-present, so the reset now happens there (guarded by
!isPreloading). Rewire the :app: UI test to embed the paywall via PaywallBuilder
instead of register(), so it exercises the embedded cache-hit reset.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6NCb2BMXSXwAN1vCMFvyc
Injects a redeemExistingCodes suspend closure into TransactionManager and
invokes it in didPurchase (both internal and external/observer success
branches) and didRestore. The closure is wired in DependencyContainer to
call reedemer.redeem(RedeemType.Existing), following the
AttributionManager.redeemAfterSetting precedent so TransactionManager never
references the redeemer directly.

Mirrors iOS PR #490 / SW-5516.
…set-on-represent

Fix dead buy button when re-presenting a cached paywall after a purchase
…ase-restore

# Conflicts:
#	.benchmark/REPORT.md
#	.benchmark/results/preload-benchmark-high.json
#	.benchmark/results/preload-benchmark-low.json
#	.benchmark/results/preload-benchmark-mid.json
Always redeem existing web codes after a successful purchase and on restore
When a custom PurchaseController completes a purchase on a non-Play
store (e.g. Galaxy Store), Google Play Billing never fires
onPurchasesUpdated, so getLatestTransaction() suspended forever on the
purchaseResults StateFlow. Paywall purchases then never resolved: no
transaction_complete, no dismissal, spinner stuck indefinitely.

- Bound the wait with a 5s timeout when an external purchase controller
  is in use; the controller's result is the source of truth and the Play
  transaction only enriches tracking events, so fall back to null.
- Clear the retained purchaseResults value when a purchase starts so a
  purchase can't be resolved against a stale transaction from an
  earlier one (StateFlow retains the last emission indefinitely).
- Remove a redundant duplicate getLatestTransaction() await in the
  external purchase path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YTvfzggPBxHokZmZdauvwo
prepareToPurchase now clears billing.purchaseResults at purchase start,
which the strict mock in the androidTest TransactionManagerTest didn't
stub, failing internal purchase tests with MockKException.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YTvfzggPBxHokZmZdauvwo
…ang-apgr3k

Fix Galaxy Store hang by timing out transaction lookup
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants