Goal
When a user achieves a milestone (e.g. β7βday streakβ, β100 workoutsβ, βfirst purchaseβ), show a fullscreen congratulations dialog. Key constraints:- No imperative UI calls from business logic (your feature code never calls
showDialog) - Fully declarative: reaching a milestone updates state; a popup host reacts to state changes
- Safe + testable: eligibility is in a guard, and βshown/dismissedβ is tracked via storage
1) Surfaces + variants
2) Domain: milestone payload + option + item
3) βUser progress storeβ (the only thing your app updates)
Your product logic can update this store from anywhere (BLoC, Riverpod, Provider, your own service).4) Provider: generate candidates from current app state
This provider maps βuser progressβ β candidate milestone items.5) Guard: βshow onceβ + queue behavior
Hereβs a simple rule:- if it was dismissed (or shown once, depending on your policy), donβt show again
- otherwise schedule per surface by priority
refresh so the guard re-runs whenever progress changes:
6) Popup host: the only place that βshows UIβ
Your business logic never callsshowDialog. The host watches AppSurface.popup and does it automatically.
This recipe uses the built-in PresentumPopupSurfaceStateMixin described in Popup hosts.
7) Dialog widget: read item from InheritedPresentumItem
Why this pattern scales
- Feature code is pure state: update
progress.markReached(...) - Eligibility is centralized: show-once, cooldowns, queueing, priorities live in guards
- UI is reactive: popup host reacts to Presentum state, so you donβt sprinkle dialog logic through the app