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