Propagate default package across workflow steps#3431
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3431 +/- ##
=======================================
Coverage 79.47% 79.47%
=======================================
Files 362 362
Lines 14547 14547
Branches 1977 1977
=======================================
Hits 11561 11561
Misses 2190 2190
Partials 796 796 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Introduce setContextPackage()/contextPackageInfo on Components so the ViewModel can supply a default package for steps that have no own package components; selectedPackageInfo falls back to contextPackageInfo when selectedPackageUniqueId is null. Remove selectPackageIfExists and its call site in PaywallViewModel as part of migrating to the new context-based propagation approach. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rkflow steps Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…kage approach Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…stead of per-step default_package_id Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ckage idempotent - Restore deleted JSON parsing tests for single_step_fallback_id in WorkflowDetailResolverTest - Move idempotency guard into setContextPackage so the set-once contract is self-enforcing - Add Logger.w when singleStepFallbackId points to a missing step - Add test for graceful degradation when singleStepFallbackId references a non-existent step Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…m:RevenueCat/purchases-android into cesar/2026-05-03-workflow-package-context
…o all steps Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
I had a few comments that gh does not let me make 😢 |
facumenzella
left a comment
There was a problem hiding this comment.
rejecting till GitHub lets me put my comments 🙏
| if (newState is PaywallState.Loaded.Components) { | ||
| val defaultPackage = workflow.singleStepFallbackId | ||
| ?.let { workflowStepStateCache[it]?.selectedPackageInfo } | ||
| if (defaultPackage != null) { | ||
| newState.setDefaultPackage(defaultPackage) | ||
| } | ||
| } |
There was a problem hiding this comment.
I wonder if we could do this at the prewarm stage instead? 🤔
There was a problem hiding this comment.
Agreed, but it needs to be here as well for the first step. So the prewarm of the following steps have this (it's idempotent only happens once) so when they load it's ready. Basically:
Prewarmed steps don’t have their defaultPackageInfo set until the user navigates to them. If stepStates is observed by the UI between prewarm completing and actual navigation, those states would have defaultPackageInfo = null. The initial step is built synchronously before prewarm even starts, so the initial step would lose its setDefaultPackage call. So we need to add the setDefaultPackage call inside preWarmWorkflowStepCache but keep it in buildStateFromStep for the initial step.
facumenzella
left a comment
There was a problem hiding this comment.
I think it looks good. Just one nit open for debate 👍
**This is an automatic release.** ## RevenueCat SDK ### 🐞 Bugfixes * fix: url encode query prameters (RevenueCat#3451) via Jacob Rakidzich (@JZDesign) ## RevenueCatUI SDK ### 🐞 Bugfixes * Fix: dismiss was called before onPurchaseComplete callback invocation (RevenueCat#3353) via Jacob Rakidzich (@JZDesign) * Propagate default package across workflow steps (RevenueCat#3431) via Cesar de la Vega (@vegaro) ### Paywallv2 #### ✨ New Features * feat: Allow disabling of automatic font scaling (RevenueCat#3438) via Jacob Rakidzich (@JZDesign) ### 🔄 Other Changes * Extract `PaywallComponentsImagePreDownloader` (RevenueCat#3448) via Cesar de la Vega (@vegaro) * Simplify `WorkflowTransitionState` with explicit from/to step fields (RevenueCat#3441) via Cesar de la Vega (@vegaro) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk release bookkeeping: primarily flips version strings from `10.5.0-SNAPSHOT` to `10.5.0` and updates docs/changelogs, with no functional code changes beyond the reported version constant. > > **Overview** > Cuts the `10.5.0` release by switching the project from `10.5.0-SNAPSHOT` to `10.5.0` across build metadata (`.version`, `gradle.properties`, sample/test app `libs.versions.toml`, and `Config.frameworkVersion`). > > Updates release artifacts and documentation pointers: CircleCI docs deploy now syncs the `10.5.0` docs folder to S3, `docs/index.html` redirects to `10.5.0`, and changelogs are rolled forward with the `10.5.0` entries in `CHANGELOG.md`/`CHANGELOG.latest.md`. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 48537d6. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…at#3445) ## Summary Follow-up to RevenueCat#3431. During review of RevenueCat#3431 I noticed that when `singleStepFallbackId` points to the same step as `initialStepId`, the pre-computation guard (`stepWithPackages.id != initialStep.id`) correctly skips re-building the step, but there was no test covering this path. In this edge case `setDefaultPackage` is effectively called on the step with its own `selectedPackageInfo` as input (a self-referential read from the cache). The test verifies that: - No crash occurs - `ownSelection` takes precedence — MONTHLY (the default-by-config package) is still returned correctly ## Test plan - [x] New test passes locally: `singleStepFallbackId equal to initialStepId does not crash and own selection is used` - [x] Full `PaywallViewModelWorkflowTest` suite passes 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: adds a unit test only, with no production code changes; the main impact is tighter regression coverage around workflow default package selection. > > **Overview** > Adds a new `PaywallViewModelWorkflowTest` case covering the edge scenario where `workflow.singleStepFallbackId` equals `initialStepId`. > > The test asserts `updateStateFromWorkflow` doesn’t crash and that the initial step’s *own* default package selection still wins (e.g. MONTHLY) rather than being overridden by a self-referential fallback/default package lookup. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit fd9659d. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Motivation
In a multipage workflow paywall,
PackageComponents only live on the terminal step. Early "info" screens have no package components, soselectedPackageInfoisnullon them. Price/period template variables like{{ price }}and{{ price_per_year }}can't resolve on those screens.Description
Adds a default-package fallback. The terminal step is identified by
singleStepFallbackIdon the workflow (added in #3436). When any step state is built, the terminal step'sselectedPackageInfois applied as the default. Steps with their ownPackageComponents ignore the default; steps without one resolve variables against it.The default is set once per step and never overwritten, so back navigation always shows the same content as the initial render, even if the user changed the selection on the terminal step.
Changes:
PaywallState.Loaded.Components: adddefaultPackageInfoandsetDefaultPackage(info).setDefaultPackageis idempotent (set once, never overwritten).selectedPackageInfois nowownSelection ?: defaultPackageInfo.PaywallViewModel: inupdateStateFromWorkflow, pre-compute thesingleStepFallbackIdstep first so its default is in cache before any other step loads. On every step build, apply the fallback step'sselectedPackageInfo(idempotency means it only takes effect on the first visit). Log a warning ifsingleStepFallbackIdpoints to a missing step.singleStepFallbackIdis missing. Plus extraWorkflowNavigatorcases (deep backstack, non-linear navigation).Note
Medium Risk
Touches workflow state construction/caching and selection precedence, which can subtly affect navigation/backstack behavior and displayed pricing context across steps.
Overview
Adds a default-package fallback to multi-step workflow paywalls:
PaywallState.Loaded.Componentsnow stores an idempotentdefaultPackageInfo, andselectedPackageInforesolves as own selection falling back to that default.PaywallViewModelnow precomputes thesingleStepFallbackIdstep to seed the cache, applies the fallback step’sselectedPackageInfoas a default on every step build/prewarm (without overwriting on revisits), and logs when the fallback step ID is missing.Expands tests to cover context propagation on packageless steps, back-navigation stability, selection precedence, missing-fallback safety, plus additional
WorkflowNavigatorbackstack/loop scenarios.Reviewed by Cursor Bugbot for commit a9a862d. Bugbot is set up for automated code reviews on this repo. Configure here.