Workflow state & ViewModel infrastructure#3416
Conversation
f0bc422 to
0c815c2
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3416 +/- ##
=======================================
Coverage 79.45% 79.45%
=======================================
Files 362 362
Lines 14539 14539
Branches 1976 1976
=======================================
Hits 11552 11552
Misses 2190 2190
Partials 797 797 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
0c815c2 to
bb79d4e
Compare
- NavigationDirection enum (forward / backward / none) - WorkflowPaywallUiState: currentStepId, lazy step state cache, pendingTransition - WorkflowPendingTransition: fromStepId + direction + monotonic id, set atomically with currentStepId so the first composition after navigation already knows both surfaces - WorkflowNavigator: refactored from StateFlow to plain properties — navigation is now synchronous; all reactive state lives in the ViewModel - PaywallViewModel: adds workflowState / onTransitionComplete; removes navigationDirection (direction now lives in WorkflowPendingTransition); workflowStepStateCache memoizes computed step states on first visit so back-navigation is instant; rebuildWorkflowStepStates handles color-scheme changes without resetting navigation position Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bb79d4e to
2499bec
Compare
facumenzella
left a comment
There was a problem hiding this comment.
a few nits! but looks good
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rection Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
refreshStateIfLocaleChanged only mutated the current step's locale, leaving every other entry in workflowStepStateCache stale. Navigating back to a previously visited step would render text in the old locale. Propagate the new locale to all cached entries in the same pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 81298b7. Configure here.
Previously, buildStateFromStep unconditionally wrote a non-null WorkflowPaywallUiState even when computeStateForStep returned PaywallState.Error. This left workflowState non-null (implying active workflow mode) while _state was Error and stepStates[currentStepId] was absent (errors are not cached). Setting workflowState to null on error lets the UI fall through to the normal error rendering path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@facumenzella I pushed some small commits after Cursor's suggestions. Mind taking a look? |
| private var currentStepId: String = workflow.initialStepId | ||
|
|
||
| private val backStack = ArrayDeque<String>() | ||
| val backStackSnapshot: List<String> get() = backStack |
There was a problem hiding this comment.
maybe it's worth mentioning it's just for testing purposes?
### Motivation The lazy step-state cache from RevenueCat#3416 already keeps back-navigation instant (each step is memoized on first visit). But the *first* forward navigation to a never-visited step still pays the full `calculateState` cost on the main thread, which can drop frames on a slide animation. ### Description Adds `preWarmWorkflowStepCache(...)`, a coroutine launched on `viewModelScope` immediately after the initial step is built. It iterates every step in the workflow, computes its state on `Dispatchers.Default`, and stores the result in `workflowStepStateCache`. Once all steps finish computing, `_workflowState.stepStates` is updated in a single batch write. The job is cancelled and the cache cleared whenever a new workflow loads, so a workflow swap doesn't leak stale states or compute work. Pure perf optimization — `buildStateFromStep` already handles a missing cache entry by computing on demand, so behavior is identical with or without the pre-warm. The only observable difference is that forward navigation to a never-visited step is no longer blocking on the main thread once the pre-warm has had a chance to finish. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Adds background coroutine work and shared cache mutation in `PaywallViewModelImpl`, which could introduce race conditions or extra CPU usage if cancellation/timing isn’t handled as expected. Functional behavior should remain the same, but workflow navigation state updates now occur asynchronously. > > **Overview** > Reduces jank when navigating forward in multi-step workflow paywalls by **precomputing and caching** each step’s `PaywallState` in the background. > > `PaywallViewModelImpl` now launches a `viewModelScope` job that computes missing step states on `Dispatchers.Default`, updates locale on computed states, and then batch-updates `workflowState.stepStates`; the job is cancelled and the cache cleared when a new workflow is loaded. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit a661ac3. 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 Opus 4.7 (1M context) <noreply@anthropic.com>
**This is an automatic release.** ## RevenueCat SDK ### ✨ New Features * Add optional support for setting obfuscated account id to product changes (RevenueCat#3428) via Mark Villacampa (@MarkVillacampa) ## RevenueCatUI SDK ### Paywallv2 #### ✨ New Features * Add slide transition to workflow paywalls (RevenueCat#3418) via Cesar de la Vega (@vegaro) * Workflow state & ViewModel infrastructure (RevenueCat#3416) via Cesar de la Vega (@vegaro) #### 🐞 Bugfixes * Fix paywall layout direction for RTL locale overrides (PWENG-39) (RevenueCat#3425) via Monika Mateska (@MonikaMateska) * Apply ripple shape clip on a sibling Box to avoid clipping content (RevenueCat#3395) via Toni Rico (@tonidero) ### 🔄 Other Changes * build(deps): bump fastlane-plugin-revenuecat_internal from `21e02ec` to `af7bb5c` (RevenueCat#3442) via dependabot[bot] (@dependabot[bot]) * Abstract workflow page transition animation behind sealed class (RevenueCat#3430) via Cesar de la Vega (@vegaro) * Add `single_step_fallback_id` field to `PublishedWorkflow` (RevenueCat#3436) via Cesar de la Vega (@vegaro) * build(deps): bump fastlane-plugin-revenuecat_internal from `2d11430` to `21e02ec` (RevenueCat#3429) via dependabot[bot] (@dependabot[bot]) * Generalize `PaywallComponentsScaffold` for workflow reuse (RevenueCat#3417) via Cesar de la Vega (@vegaro) * perf: pre-warm workflow paywall step states off-thread (RevenueCat#3420) via Cesar de la Vega (@vegaro) * Update baseline profiles (RevenueCat#3427) via RevenueCat Git Bot (@RCGitBot) * build(deps): bump fastlane-plugin-revenuecat_internal from `d24ab26` to `2d11430` (RevenueCat#3426) via dependabot[bot] (@dependabot[bot]) * Replace unauthenticated SDKMAN install with SHA-pinned orb command (RevenueCat#3407) via Rick (@rickvdl) * Auto load paywall in paywall tester via local.properties (RevenueCat#3405) via Cesar de la Vega (@vegaro) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: this is a version/release cut that mainly updates version strings, changelogs, and doc deployment targets with no functional logic changes beyond version identifiers. > > **Overview** > Cuts the `10.4.0` release by removing `-SNAPSHOT` across the project (core `VERSION_NAME`, `Config.frameworkVersion`, sample/test app dependency versions, and the root `.version` file). > > Updates release collateral and publishing to point at `10.4.0`, including changelogs (`CHANGELOG.md`/`CHANGELOG.latest.md`), docs redirect (`docs/index.html`), and the CircleCI `docs-deploy` S3 sync path (from `10.4.0-SNAPSHOT` to `10.4.0`). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f7b3604. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->


Motivation
Multi-step workflow paywalls need the UI to know which step is active, what the neighboring steps look like, and which direction a navigation is going, all before the first recomposition after a tap. Without this, the slide animation can't start from the correct offscreen position and the outgoing step can't be cleaned up safely.
Description
Introduces the ViewModel-side infrastructure that drives animated, multi-step workflow paywalls:
WorkflowPaywallUiState— a new@Stabledata class exposed fromPaywallViewModelasworkflowState: State<WorkflowPaywallUiState?>. It is non-null when the active paywall is a workflow, and carries:currentStepId— the step currently on screen.stepStates— a map of computedPaywallState.Loaded.Componentskeyed by step id. Populated lazily as the user navigates so any visited step is cheap to revisit. (Eager pre-warm is added on top in perf: pre-warm workflow paywall step states off-thread #3420.)pendingTransition— aWorkflowPendingTransition(from-step, direction, monotonic id) set atomically alongsidecurrentStepIdso the first recomposition after a navigation already has both surfaces and their target positions.NavigationDirection— a simpleFORWARD/BACKWARDenum used byWorkflowPendingTransitionso the UI knows which way to slide.Step state caching —
buildStateFromStepchecksworkflowStepStateCachebefore doing the heavycalculateStatework and stores the result on first visit, so back-navigation never recomputes.onTransitionComplete(transitionId: Int)— called by the UI when a slide animation ends. ClearspendingTransitionfor the matching id (guarded against stale callbacks), letting the outgoing step be removed from the Compose slot table.WorkflowNavigatorcleanup — replaces the internalMutableStateFlow<String>with a plainvar(the ViewModel already owns the single source of truth) and dropsStateFlow-related imports. ThebackStackfield is exposed directly so the new tests can assert on its contents (the class isinternal, so visibility is naturally scoped).Checklist
Note
Medium Risk
Touches paywall state management and workflow navigation paths, which could affect paywall rendering and step transitions; changes are scoped and backed by new unit tests.
Overview
Adds ViewModel-driven workflow UI state to support animated, multi-step paywall workflows.
PaywallViewModelnow exposesworkflowState(WorkflowPaywallUiState) plusonTransitionComplete(transitionId)so the UI can render the current step, cache visited stepPaywallState.Loaded.Components, and coordinate slide transitions via a monotonic transition id andNavigationDirection.Refactors workflow navigation to be less reactive (removes
StateFlowfromWorkflowNavigator), updates locale-refresh to also update cached step states, and adds unit tests covering forward/back navigation, transition cleanup, and step-state caching; mocks/previews are updated to implement the new interface members.Reviewed by Cursor Bugbot for commit eb4093f. Bugbot is set up for automated code reviews on this repo. Configure here.