Fix null Placements when offering_ids_by_placement is absent#3254
Merged
Conversation
…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>
Codecov Report✅ All modified and coverable lines are covered by tests. 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. 🚀 New features to boost your workflow:
|
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 -->
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
purchases-iosand hybridsMotivation
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.createOfferingsalways constructs anOfferings.Placementsobject whenplacementsis present, defaultingofferingIdsByPlacementtoemptyMap()ifoffering_ids_by_placementis absent sofallback_offering_idstill applies.Adds unit tests covering placements responses with only
fallback_offering_id(including explicitnull) to verifygetCurrentOfferingForPlacementcorrectly falls back or returnsnullas expected.Reviewed by Cursor Bugbot for commit ddae70b. Bugbot is set up for automated code reviews on this repo. Configure here.