AchievementService class
Local achievement/badge progress tracker (no Game Center/Play Games integration — that's a later, separate task).
This package doesn't hardcode any game-specific achievement list —
thresholds are declared by the caller up front via register (once
per achievement id, typically at app boot before any progress is
touched). incrementProgress/isCompleted then only need the id.
This was chosen over passing the threshold on every incrementProgress
call: callers add progress from many call sites scattered through game
logic, but only need to know the threshold value in one place (wherever
achievements are declared) — repeating it at every call site invites
them drifting out of sync.
Progress persists across restarts as one small JSON blob via
VersionedJsonStore (achievementId -> progress), rather than one raw
StorageService key per achievement. Thresholds themselves are not
persisted — they're cheap in-memory data the caller re-declares every
run via register, same as the rest of the game's achievement list.
- Inheritance
-
- Object
- GetLifeCycle
- DisposableInterface
- GetxService
- AchievementService
Constructors
- AchievementService({String? storageKey, StorageService? storage})
-
storage(BUG-90) lets a caller inject its own StorageService instance instead of relying on the ambientStorageService.to(Get.find) — useful for an isolated test/slot instance, or any caller that constructs this service beforeStorageServiceis Get.put'd. When omitted, falls back to StorageService.maybe at the point of first use (not at construction — same lazy-hydration timing as before) so an app that registers storage after constructing this service (unusual, but not previously forbidden) still works; adlogwarns once if neither is available, since that previously silently masqueraded as "no saved progress" with no way to tell the two apart.
Properties
-
debugPendingSaves
→ Future<
void> -
Awaits every save queued so far — lets a test deterministically wait
for a burst of rapid
incrementProgresscalls to fully settle instead of guessing a delay.no setter - hashCode → int
-
The hash code for this object.
no setterinherited
- initialized → bool
-
Checks whether the controller has already been initialized.
no setterinherited
- isClosed → bool
-
Checks whether the controller has already been closed.
no setterinherited
-
onDelete
→ InternalFinalCallback<
void> -
Internal callback that starts the cycle of this controller.
finalinherited
-
onStart
→ InternalFinalCallback<
void> -
Called at the exact moment the widget is allocated in memory.
It uses an internal "callable" type, to avoid any @overrides in subclases.
This method should be internal and is required to define the
lifetime cycle of the subclass.
finalinherited
-
onUnlock
→ Stream<
String> -
Fires exactly once per achievement id, the moment incrementProgress
pushes it from not-completed to completed (IDEA-43). Never fires from
register alone — even if a newly-declared threshold is already
retroactively met by existing progress — and never fires again from
further increments once already completed.
no setter
-
progressSnapshot
→ Map<
String, int> -
Detached, unmodifiable snapshot of stored progress.
no setter
- runtimeType → Type
-
A representation of the runtime type of the object.
no setterinherited
Methods
-
$configureLifeCycle(
) → void -
inherited
-
incrementProgress(
String achievementId, int amount) → void -
Adds
amounttoachievementId's progress. Safe to call after the achievement is already completed: progress keeps accumulating, isCompleted staystrue, never throws or "re-unlocks". -
incrementProgressDurably(
String achievementId, int amount) → Future< SdkResult< int> > - Awaitable counterpart to incrementProgress (BUG-90) — applies the SAME increment (identical validation, in-memory mutation, unlock semantics), then awaits the save it triggers and reports whether that save actually reached disk. Returns SdkFailure (kind SdkErrorKind.storage) when it didn't — e.g. no StorageService available, or the underlying write threw — without undoing the in-memory progress: a caller that gets a failure already has an accurate in-RAM progress value to show, it just additionally knows that value isn't durable yet and may retry/alert. Reach for this at any call site that cares whether an unlock is actually saved (e.g. a "load-bearing" one-time reward); gameplay call sites that don't need that proof can keep using the simpler sync incrementProgress.
-
isCompleted(
String achievementId) → bool -
trueonce progress reaches the registered threshold.false(never throws) for an id that was never registered or has no progress yet. -
mergeProgressDurably(
Map< String, int> remote) → Future<SdkResult< int> > - Merges by max and persists; storage failure retains in-memory progress.
-
noSuchMethod(
Invocation invocation) → dynamic -
Invoked when a nonexistent method or property is accessed.
inherited
-
onClose(
) → void -
Called before onDelete method. onClose might be used to
dispose resources used by the controller. Like closing events,
or streams before the controller is destroyed.
Or dispose objects that can potentially create some memory leaks,
like TextEditingControllers, AnimationControllers.
Might be useful as well to persist some data on disk.
override
-
onInit(
) → void -
Called immediately after the widget is allocated in memory.
You might use this to initialize something for the controller.
inherited
-
onReady(
) → void -
Called 1 frame after onInit(). It is the perfect place to enter
navigation events, like snackbar, dialogs, or a new route, or
async request.
inherited
-
progressOf(
String achievementId) → int -
Current progress for
achievementId—0(never throws) for an id with no progress yet, whether or not it was ever registered. Keeps reflecting the true accumulated value even past the threshold (an already-completed achievement isn't "capped" at its threshold here). -
progressRatio(
String achievementId) → double -
progressOf(id) / thresholdOf(id)clamped to[0.0, 1.0]—0.0(never throws) ifachievementIdwas never registered or has no progress yet. Ready to feed straight into a progress-bar-shaped widget (ProgressBarStars,CircularProgressRing) without the caller having to null-check thresholdOf or clamp progressOf's uncapped value. -
register(
String achievementId, int threshold) → void -
Declares
achievementId'sthresholdup front. Safe to call again for the same id (e.g. re-declared every app boot) — only updates the threshold, never touches stored progress. -
thresholdOf(
String achievementId) → int? -
The threshold declared via register for
achievementId, ornullif it was never registered — distinct from isCompleted's treatment of "never registered" asfalse, since a progress-bar UI needs to tell "no achievement declared" apart from "declared, not yet met". -
toString(
) → String -
A string representation of this object.
inherited
Operators
-
operator ==(
Object other) → bool -
The equality operator.
inherited
Static Properties
- maybe → AchievementService?
-
Gets the instance if already registered (safe to call from
game/widget tests).
no setter