Skip to content

Fix null Placements when offering_ids_by_placement is absent#3254

Merged
tonidero merged 1 commit into
mainfrom
dan/pw-1159-bug-null-placements-on-android
Apr 27, 2026
Merged

Fix null Placements when offering_ids_by_placement is absent#3254
tonidero merged 1 commit into
mainfrom
dan/pw-1159-bug-null-placements-on-android

Conversation

@dpannasch

@dpannasch dpannasch commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

When the backend sends a placements object with only fallback_offering_id and no offering_ids_by_placement key (e.g., "all placements" targeting or no targeting match), the Android SDK was discarding the entire Placements object — including the fallback. This caused getCurrentOfferingForPlacement to return null instead of the fallback offering, diverging from iOS behavior.

Default offeringIdsByPlacement to an empty map when the key is missing, matching iOS's @DefaultDecodable.EmptyDictionary behavior.

Checklist

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

Motivation

Description


Note

Medium Risk
Medium risk because it changes how backend JSON is interpreted for placements, which can affect which offering is returned for a placement. Scope is small and covered by new unit tests for the missing-key cases.

Overview
Ensures OfferingParser.createOfferings always constructs an Offerings.Placements object when placements is present, defaulting offeringIdsByPlacement to emptyMap() if offering_ids_by_placement is absent so fallback_offering_id still applies.

Adds unit tests covering placements responses with only fallback_offering_id (including explicit null) to verify getCurrentOfferingForPlacement correctly falls back or returns null as expected.

Reviewed by Cursor Bugbot for commit ddae70b. Bugbot is set up for automated code reviews on this repo. Configure here.

…ponse

When the backend sends a placements object with only fallback_offering_id
and no offering_ids_by_placement key (e.g., "all placements" targeting or
no targeting match), the Android SDK was discarding the entire Placements
object — including the fallback. This caused getCurrentOfferingForPlacement
to return null instead of the fallback offering, diverging from iOS behavior.

Default offeringIdsByPlacement to an empty map when the key is missing,
matching iOS's @DefaultDecodable.EmptyDictionary behavior.

Fixes: PW-1159

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@tonidero tonidero added the pr:fix A bug fix label Apr 27, 2026
@tonidero tonidero marked this pull request as ready for review April 27, 2026 08:20
@tonidero tonidero requested a review from a team as a code owner April 27, 2026 08:20
@codecov

codecov Bot commented Apr 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.41%. Comparing base (10bb1ec) to head (ddae70b).
⚠️ Report is 109 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3254      +/-   ##
==========================================
+ Coverage   79.39%   79.41%   +0.01%     
==========================================
  Files         356      356              
  Lines       14346    14344       -2     
  Branches     1959     1959              
==========================================
+ Hits        11390    11391       +1     
+ Misses       2152     2150       -2     
+ Partials      804      803       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tonidero tonidero enabled auto-merge April 27, 2026 08:33

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

Thanks for the fix!!

@tonidero tonidero added this pull request to the merge queue Apr 27, 2026

@ajpallares ajpallares left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch!

Merged via the queue into main with commit 78771c7 Apr 27, 2026
36 checks passed
@tonidero tonidero deleted the dan/pw-1159-bug-null-placements-on-android branch April 27, 2026 08:53
matteinn pushed a commit to matteinn/purchases-android that referenced this pull request May 5, 2026
**This is an automatic release.**

## RevenueCat SDK
### ✨ New Features
* Unified StoreReplacementMode API (RevenueCat#3234) via Will Taylor
(@fire-at-will)
* Add placement and targeting context to paywall events (RevenueCat#3253) via Dan
Pannasch (@dpannasch)
### 🐞 Bugfixes
* Fix null Placements when offering_ids_by_placement is absent (RevenueCat#3254)
via Dan Pannasch (@dpannasch)

## RevenueCatUI SDK
### Paywallv2
#### ✨ New Features
* Wire multipage workflow navigation into PaywallViewModel (RevenueCat#3381) via
Cesar de la Vega (@vegaro)

### 🔄 Other Changes
* Add `triggerType` to `WorkflowTrigger` (RevenueCat#3393) via Cesar de la Vega
(@vegaro)
* Extract private function `NavigateTo.toPaywallAction` (RevenueCat#3392) via
Cesar de la Vega (@vegaro)
* Bump revenucatui-tests gradle cache key (RevenueCat#3391) via Toni Rico
(@tonidero)
* Create `WorkflowTriggerType` and `WorkflowTriggerActionType` (RevenueCat#3386)
via Cesar de la Vega (@vegaro)
* Update baseline profiles (RevenueCat#3390) via RevenueCat Git Bot (@RCGitBot)
* Plumb `componentId` through buttons on workflow interactions (RevenueCat#3380)
via Cesar de la Vega (@vegaro)
* Add `ButtonComponent.Action.Workflow` (RevenueCat#3385) via Cesar de la Vega
(@vegaro)
* Add `componentId` to `ButtonCoomponentStyle` (RevenueCat#3384) via Cesar de la
Vega (@vegaro)
* Migrate all suspendCoroutine usages to suspendCancellableCoroutine
(RevenueCat#3365) via Jaewoong Eum (@skydoves)
* Add `WorkflowNavigator` for multipage workflow step navigation (RevenueCat#3379)
via Cesar de la Vega (@vegaro)
* build(deps): bump fastlane-plugin-revenuecat_internal from `b822f01`
to `d24ab26` (RevenueCat#3383) via dependabot[bot] (@dependabot[bot])
* Add `id` field to `ButtonComponent` (RevenueCat#3377) via Cesar de la Vega
(@vegaro)
* Add CI workflows for generating Baseline Profiles (RevenueCat#3372) via Jaewoong
Eum (@skydoves)
* add min sdk level for paywalls and customer center (RevenueCat#2465) via
Muhammad-Sharif Moustafa (@mshmoustafa)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk release bookkeeping: updates version strings, changelogs, and
documentation deployment targets without changing runtime logic beyond
the reported version identifier.
> 
> **Overview**
> Cuts the `10.3.0` release by updating all version references from
`10.3.0-SNAPSHOT` to `10.3.0` across build config (`gradle.properties`,
`.version`, `Config.frameworkVersion`) and sample/test app dependency
pins.
> 
> Updates release documentation publishing to sync Dokka output to the
`10.3.0` S3 path and changes `docs/index.html` to redirect to `10.3.0`.
Also promotes release notes by moving `CHANGELOG.latest.md` entries into
a new `CHANGELOG.md` section for `10.3.0`.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
056ce62. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:fix A bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants