Skip to main content

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).
No dialogs. No navigation. Just state.

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
Use refresh so the guard re-runs whenever progress changes:

6) Popup host: the only place that β€œshows UI”

Your business logic never calls showDialog. 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