Skip to content

fix: ensure activity is attached before showing in-app messages#3274

Merged
tonidero merged 1 commit into
RevenueCat:external/matteinn/fix/#3273-showinappmessagesifneeded-crashfrom
matteinn:fix/#3273-showinappmessagesifneeded-crash
Mar 25, 2026
Merged

fix: ensure activity is attached before showing in-app messages#3274
tonidero merged 1 commit into
RevenueCat:external/matteinn/fix/#3273-showinappmessagesifneeded-crashfrom
matteinn:fix/#3273-showinappmessagesifneeded-crash

Conversation

@matteinn

@matteinn matteinn commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

Checklist

  • If applicable, unit tests
  • If applicable, create follow-up issues for purchases-ios and hybrids

Motivation

This PR adds an additional safety check in BillingWrapper.showInAppMessagesIfNeeded to ensure the activity is fully attached to a window before attempting to show Google Play in-app messages, preventing crashes.

Resolves #3273.

Description

While isFinishing and isDestroyed are already checked, NullPointerException crashes can still happen while accessing a window token from a null or detached view.
Using peekDecorView().windowToken safely ensures the activity has a decor view and is attached without accidentally triggering view creation.


Note

Low Risk
Low risk: adds an extra null/attachment guard before calling Google Play in-app messaging, reducing crash risk with minimal behavior change (messages may be skipped in more edge cases).

Overview
Prevents potential crashes when showing Google Play in-app messages by adding a new guard in BillingWrapper.showInAppMessagesIfNeeded to ensure the Activity is actually attached to a window (window.peekDecorView().windowToken present) before calling showInAppMessages.

Updates tests to use a helper for an attached Activity and adds coverage for the new early-return cases when window, decor view, or windowToken is missing.

Written by Cursor Bugbot for commit af9df05. This will update automatically on new commits. Configure here.

@matteinn matteinn requested a review from a team as a code owner March 25, 2026 10:10

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

Thank you for the contribution!

Left a question in the issue, to make sure this is needed. Other than that, it would be great to have a test for this new case :)

Thanks again!

@matteinn matteinn force-pushed the fix/#3273-showinappmessagesifneeded-crash branch from 8f092a2 to af9df05 Compare March 25, 2026 13:10
@matteinn matteinn requested a review from tonidero March 25, 2026 13:13
@tonidero tonidero changed the base branch from main to external/matteinn/fix/#3273-showinappmessagesifneeded-crash March 25, 2026 14:52

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

Amazing, thanks for adding those tests and thanks again for the contribution! We will get this merged and will be part of our next release 👍

@tonidero tonidero merged commit d3a5a90 into RevenueCat:external/matteinn/fix/#3273-showinappmessagesifneeded-crash Mar 25, 2026
1 check passed
github-merge-queue Bot pushed a commit that referenced this pull request Mar 25, 2026
…sages (#3274) contributed by @matteinn (#3275)

<!-- Thank you for contributing to Purchases! Before pressing the
"Create Pull Request" button, please provide the following: -->

### Checklist
- [ ] If applicable, unit tests
- [ ] If applicable, create follow-up issues for `purchases-ios` and
hybrids

### Motivation
This PR adds an additional safety check in
BillingWrapper.showInAppMessagesIfNeeded to ensure the activity is fully
attached to a window before attempting to show Google Play in-app
messages, preventing crashes.

Resolves #3273.

### Description
While `isFinishing` and `isDestroyed` are already checked,
`NullPointerException` crashes can still happen while accessing a window
token from a null or detached view.
Using `peekDecorView().windowToken` safely ensures the activity has a
decor view and is attached [without accidentally triggering view
creation](https://developer.android.com/reference/android/view/Window#peekDecorView()).

contributed by @matteinn in #3274 

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk guard change that only adds an extra precondition before
invoking Google Play Billing in-app messages; main impact is potentially
skipping message display in edge lifecycle states to avoid crashes.
> 
> **Overview**
> Prevents crashes when showing Google Play in-app messages by adding an
additional lifecycle safety check in
`BillingWrapper.showInAppMessagesIfNeeded`: it now verifies the
`Activity` is attached to a window via
`activity.window?.peekDecorView()?.windowToken` before calling
`showInAppMessages`.
> 
> Updates `BillingWrapperTest` to use a reusable
`mockAttachedActivity()` helper and adds coverage to ensure in-app
messages are *not* shown when the window, decor view, or window token
are missing.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
d3a5a90. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: Matteo Innocenti <matteo@appstyx.com>
github-merge-queue Bot pushed a commit that referenced this pull request Mar 26, 2026
**This is an automatic release.**

## RevenueCat SDK
### 🐞 Bugfixes
* [EXTERNAL] fix: ensure activity is attached before showing in-app
messages (#3274) contributed by @matteinn (#3275) via Toni Rico
(@tonidero)
* Ensure MediaPlayer has dedicated thread owner that is not the main
thread (#3148) via Jacob Rakidzich (@JZDesign)
* Fix heartbeat monitor and Slack notifications for nightly integration
tests (#3259) via Rick (@rickvdl)

## RevenueCatUI SDK
### Paywallv2
#### ✨ New Features
* Feat: Restore gating in paywalls UI (#3171) via Jacob Rakidzich
(@JZDesign)

### 🔄 Other Changes
* security: pin GitHub Actions to SHA hashes (#3272) via Alfonso
Embid-Desmet (@alfondotnet)
* Bump activesupport from 8.0.2 to 8.0.4.1 (#3270) via dependabot[bot]
(@dependabot[bot])
* Merge release PR after deploy (#3269) via Antonio Pallares
(@ajpallares)
* Require PR approval before release tagging (#3268) via Antonio
Pallares (@ajpallares)
* Bump json from 2.18.1 to 2.19.2 (#3261) via dependabot[bot]
(@dependabot[bot])
* feat(ads): update admob sample app (#3264) via Peter Porfy
(@peterporfy)
* feat(ads): add vanilla-ad-tracker-sample (#3263) via Peter Porfy
(@peterporfy)
* [Purchase Tester]: Persist appUserId on login screen across app
launches (#3266) via Will Taylor (@fire-at-will)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Mostly release automation and versioning changes, but it modifies
CI/orb references and deploy/merge automation, which could affect the
release pipeline if misconfigured.
> 
> **Overview**
> Cuts the `9.28.0` release by removing `-SNAPSHOT` across version
sources (`.version`, `gradle.properties`, `Config.frameworkVersion`) and
updating sample/test-app dependency pins to `9.28.0`.
> 
> Updates release documentation: publishes Dokka docs to the `9.28.0` S3
path, updates `docs/index.html` redirect to `9.28.0`, and rolls
`CHANGELOG.latest.md` into a new `9.28.0` section in `CHANGELOG.md`.
> 
> Tweaks release tooling/CI: pins `fastlane-plugin-revenuecat_internal`
to a specific git ref (and bumps a few Ruby deps), switches the CircleCI
`revenuecat/sdks-common-config` orb to a dev commit, and adds a
temporary `test_merge_queue` workflow to exercise
`revenuecat/merge-release-pr` with `use_merge_queue: true`.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
5050888. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
MonikaMateska pushed a commit that referenced this pull request Apr 20, 2026
… runtime crashes (#3367)

<!-- Thank you for contributing to Purchases! Before pressing the
"Create Pull Request" button, please provide the following: -->

### Checklist
- [x] If applicable, unit tests
- [ ] If applicable, create follow-up issues for `purchases-ios` and
hybrids

### Motivation
<!-- Why is this change required? What problem does it solve? -->
<!-- Please link to issues following this format: Resolves #999999 -->
This PR hardens `showInAppMessagesIfNeeded` against a remaining Play
Billing crash path.
Resolves #3273,
which has not been fully solved by a previous
[tentative](#3274).

### Description
<!-- Describe your changes in detail -->
<!-- Please describe in detail how you tested your changes -->

Even after lifecycle/window attachment checks,
`BillingClient.showInAppMessages(...)` can still throw a
`RuntimeException` (including `NullPointerException`) due to an internal
race in Play Billing.

This is a defensive boundary around third-party BillingClient behavior:
it complements previous activity/window checks by handling exceptions
that can still occur between pre-checks and BillingClient’s internal
view/token access.

Here's exactly what changed:
- Wrapped the `showInAppMessages(...)` invocation in `BillingWrapper`
with a try/catch
- On exception, log the error and skip showing the in-app message
instead of crashing the app
- Added new log string: BILLING_INAPP_MESSAGE_SHOW_EXCEPTION
- Added a regression unit test that simulates `showInAppMessages(...)`
throwing a `NullPointerException` and verifies that there are no crash
and that the error is logged

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk defensive change that only adds exception handling and
logging around a third-party call; behavior changes only when Play
Billing throws unexpectedly.
> 
> **Overview**
> Prevents a remaining Play Billing crash path by wrapping
`BillingClient.showInAppMessages(...)` in
`BillingWrapper.showInAppMessagesIfNeeded` with a `try/catch` and
logging an error instead of crashing.
> 
> Adds a new log string (`BILLING_INAPP_MESSAGE_SHOW_EXCEPTION`) and a
regression unit test that simulates `showInAppMessages` throwing (e.g.,
`NullPointerException`) and asserts it is logged and does not crash.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
5e8afd5. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
github-merge-queue Bot pushed a commit that referenced this pull request Apr 20, 2026
… runtime crashes (#3367) by @matteinn (#3368)

<!-- Thank you for contributing to Purchases! Before pressing the
"Create Pull Request" button, please provide the following: -->

### Checklist
- [x] If applicable, unit tests
- [ ] If applicable, create follow-up issues for `purchases-ios` and
hybrids

### Motivation
<!-- Why is this change required? What problem does it solve? -->
<!-- Please link to issues following this format: Resolves #999999 -->
This PR hardens `showInAppMessagesIfNeeded` against a remaining Play
Billing crash path.
Resolves #3273,
which has not been fully solved by a previous
[tentative](#3274).

### Description
<!-- Describe your changes in detail -->
<!-- Please describe in detail how you tested your changes -->

Even after lifecycle/window attachment checks,
`BillingClient.showInAppMessages(...)` can still throw a
`RuntimeException` (including `NullPointerException`) due to an internal
race in Play Billing.

This is a defensive boundary around third-party BillingClient behavior:
it complements previous activity/window checks by handling exceptions
that can still occur between pre-checks and BillingClient’s internal
view/token access.

Here's exactly what changed:
- Wrapped the `showInAppMessages(...)` invocation in `BillingWrapper`
with a try/catch
- On exception, log the error and skip showing the in-app message
instead of crashing the app
- Added new log string: BILLING_INAPP_MESSAGE_SHOW_EXCEPTION
- Added a regression unit test that simulates `showInAppMessages(...)`
throwing a `NullPointerException` and verifies that there are no crash
and that the error is logged


<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Small defensive change limited to in-app message display: it only adds
exception handling and logging, plus a unit test, with minimal behavior
impact beyond avoiding a crash.
> 
> **Overview**
> Prevents app crashes when Google Play Billing’s
`BillingClient.showInAppMessages` throws a `RuntimeException` despite
lifecycle/window checks, by wrapping the call in a `try/catch` and
logging a new `BILLING_INAPP_MESSAGE_SHOW_EXCEPTION` error instead of
propagating the crash.
> 
> Adds a regression test that forces `showInAppMessages` to throw (e.g.,
`NullPointerException`) and asserts the wrapper logs the error and
continues without crashing.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
fc5cd08. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->



<!-- Thank you for contributing to Purchases! Before pressing the
"Create Pull Request" button, please provide the following: -->

### Checklist
- [ ] If applicable, unit tests
- [ ] If applicable, create follow-up issues for `purchases-ios` and
hybrids

### Motivation
<!-- Why is this change required? What problem does it solve? -->
<!-- Please link to issues following this format: Resolves #999999 -->

### Description
<!-- Describe your changes in detail -->
<!-- Please describe in detail how you tested your changes -->

---------

Co-authored-by: Matteo Innocenti <matteo@appstyx.com>
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.

NPE BillingWrapper$showInAppMessagesIfNeeded$2$2.invoke

2 participants