-
Notifications
You must be signed in to change notification settings - Fork 206
getCurrentOfferingForPlacement returns null on Android but returns fallback offering on iOS #1700
Description
Environment
- purchases_flutter: 9.15.1
- purchases_ui_flutter: 9.15.1
- Flutter: 3.32.x
- iOS: 26.2 (Simulator)
- Android: API 36 (Physical device — Samsung SM-A065F)
Description
Purchases.getCurrentOfferingForPlacement(placementId) behaves differently on iOS and Android when the placement does not exist in the RevenueCat dashboard.
- iOS: Returns the current (default) offering as a fallback when the placement is not found.
- Android: Returns
nullwhen the placement is not found.
This inconsistency causes a critical business logic bug in apps that use placement-based paywall resolution with a fallback chain.
Steps to Reproduce
- Configure a RevenueCat project with a current (default) offering set.
- Do NOT create any placements in the RevenueCat dashboard.
- Call
Purchases.getCurrentOfferingForPlacement('any_nonexistent_id'). - Observe the return value on both platforms.
Expected Behavior
Both platforms should return null when the placement does not exist (or both should return the fallback — but behavior should be consistent).
Actual Behavior
| Platform | Placement exists? | Return value |
|---|---|---|
| iOS | No | default_offering (current offering fallback) |
| Android | No | null |
Debug Logs
iOS (Simulator)
[Paywall] placementId: whatsapp
[Paywall] offerings.current: default_offering
[Paywall] placementOffering: default_offering ← fallback returned
[Paywall] Step 1: presenting placement (default_offering)
Android (Physical device)
[Paywall] placementId: whatsapp
[Paywall] offerings.current: default_offering
[Paywall] placementOffering: null ← null returned
[Paywall] Step 1: placement not found, skipping
Impact
In our paywall resolution chain:
1. Try placement offering → presentPaywallIfNeeded (subscription check)
2. If no placementId → try current offering → presentPaywallIfNeeded (subscription check)
3. Try credit offering → presentPaywall (NO subscription check)
4. Fallback → presentPaywall
On Android, when the placement returns null:
- Step 1 is skipped (no offering found)
- Step 2 is skipped (
placementIdis not null, so theif (placementId == null)guard prevents execution) - Step 3 executes — showing credit-only packages to non-subscribers
This means non-subscribed users on Android can see and purchase credit packages without a subscription, bypassing the intended paywall flow. This does not happen on iOS because the fallback offering triggers the subscription check in Step 1.
Workaround
Remove the if (placementId == null) guard on Step 2, so the subscription offering check runs regardless of whether a placement was attempted. Additionally, add a subscription entitlement check before Step 3.