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