Skip to content

Add async offering APIs alternative (preloading all and deprecating)#3481

Draft
vegaro wants to merge 15 commits into
mainfrom
cesar/workflow-prefetch-and-async-offering-apis
Draft

Add async offering APIs alternative (preloading all and deprecating)#3481
vegaro wants to merge 15 commits into
mainfrom
cesar/workflow-prefetch-and-async-offering-apis

Conversation

@vegaro
Copy link
Copy Markdown
Member

@vegaro vegaro commented May 14, 2026

Motivation

With multipage paywalls we are moving the fetch of the paywall components to a separate /workflow/{offeringId} fetch per offering. Without the changes in this PR, upgrading the SDK would introduce a loading spinner on every paywall, since the paywall wouldn't be preloaded. That is a behavior change for all developers, not just those using multipage paywalls.

Description

  • getOfferings() now blocks until all workflow fetches complete in parallel before firing its callback. Existing developer contract is preserved: by the time the callback fires, paywall data is ready. Failures are best-effort, a failed workflow fetch doesn't block getOfferings(), it just means that offering may show a spinner if presented.
  • New async APIs for developers who want explicit control:
    • getCurrentOfferingWith(onError, onSuccess) / getOfferingWith(id, onError, onSuccess)
    • awaitCurrentOffering() / awaitOffering(id)
  • Offerings.current deprecated in favor of the new APIs. getOfferings() KDoc points to them.

Next major: getOfferings() stops blocking on workflow fetches; the new APIs become the only guaranteed-no-spinner path.

Usage

No changes needed for most developers. getOfferings() works as before — paywall data is ready by the time the callback fires.

Purchases.sharedInstance.getOfferings(object : ReceiveOfferingsCallback {
    override fun onReceived(offerings: Offerings) {
        // paywall data already loaded — no spinner
        showPaywall(offerings.current)
    }
    override fun onError(error: PurchasesError) { ... }
})

New: fetch a single offering directly. Use the new APIs when you want to navigate to a specific offering's paywall without going through the full Offerings object:

// Callback
Purchases.sharedInstance.getCurrentOfferingWith(
    onError = { error -> ... },
    onSuccess = { offering -> showPaywall(offering) },
)

Purchases.sharedInstance.getOfferingWith(
    id = "premium",
    onError = { error -> ... },
    onSuccess = { offering -> showPaywall(offering) },
)

// Coroutines
val offering = Purchases.sharedInstance.awaitCurrentOffering()
val offering = Purchases.sharedInstance.awaitOffering("premium")

Deprecated: Offerings.current. Accessing it directly still works but will show a deprecation warning. Migrate to the new APIs:

// Before
Purchases.sharedInstance.getOfferings(object : ReceiveOfferingsCallback {
    override fun onReceived(offerings: Offerings) {
        val current = offerings.current // ⚠️ deprecated
    }
    override fun onError(error: PurchasesError) { ... }
})

// After
Purchases.sharedInstance.getCurrentOfferingWith(
    onError = { error -> ... },
    onSuccess = { offering -> ... },
)

vegaro and others added 15 commits May 14, 2026 13:25
Adds parallel workflow fetching for all offering identifiers in a single call,
with best-effort semantics — individual fetch failures do not block onComplete.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace fire-and-forget workflowPreWarmer lambda with a blocking
fetchWorkflowsForAllOfferings() call so onSuccess is only dispatched
after workflow data for all offerings is available.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add comment explaining why vendCachedOfferingsAndMaybeRefresh bypasses workflow fetches
- Add comment explaining workflowManager is nullable when multipage paywalls not configured
- Remove vacuous error test that re-tested the happy path instead of errors
- Add test covering workflowManager = null fallback path
- Fix safe-call to non-null assertion in workflow completion test
- Add explicit cache.cachedOfferings stubs to the three new workflow tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Merge mockOfferingsManagerGetRealOfferings into mockOfferingsManagerGetOfferings
via an optional offerings parameter, eliminating the duplicated body. Add a test
for getCurrentOffering returning null when offerings.current is null.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… KDoc, add tests

- Move getCurrentOfferingWith and getOfferingWith from defaults Purchases.kt
  to defaults listenerConversions.kt as extension functions on Purchases
- Add @JvmSynthetic to both methods in listenerConversions.kt (defaults)
  and to both member functions in customEntitlementComputation/Purchases.kt
- Simplify KDoc: remove "paywall data" / "loading spinner" framing, use
  concise descriptions consistent with the rest of the public API
- Add 6 unit tests in PurchasesCommonTest covering getCurrentOfferingWith
  and getOfferingWith success, null, and error paths
Marks Offerings.current as @deprecated(WARNING) and suppresses the
warning at all internal call sites in production code.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… misleading ReplaceWith

- Swap `onSuccess`/`onError` order on `getCurrentOfferingWith` and
  `getOfferingWith` to match the `onError`-first, optional-defaulting
  convention used by every other `*With` extension in listenerConversions.kt;
  `onError` now has a default of `ON_ERROR_STUB` so trailing-lambda syntax works
- Add six tests for `awaitCurrentOffering` and `awaitOffering` in
  PurchasesCoroutinesCommonTest, covering success, null result, and error cases
- Remove the misleading `ReplaceWith` from `Offerings.current`'s `@Deprecated`
  annotation — the suggested replacement is a method on `Purchases`, not on
  `Offerings`, so the IDE quick-fix would produce broken code
- Regenerate api-defauts.txt and api-defaults-bc7.txt to reflect parameter order change

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vegaro vegaro changed the title feat: block getOfferings() on workflow fetches and add async offering APIs Add async offering APIs May 14, 2026
@RevenueCat-Danger-Bot
Copy link
Copy Markdown

1 Error
🚫 PR has label "feat:PaywallV2" but is missing required label(s): "pr:RevenueCatUI".

Generated by 🚫 Danger

@vegaro vegaro changed the title Add async offering APIs Add async offering APIs alternative A [preloading all and deprecating] May 14, 2026
@vegaro vegaro changed the title Add async offering APIs alternative A [preloading all and deprecating] Add async offering APIs alternative (preloading all and deprecating) May 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants