I think I'm missing something about the Riverpod philosophy.
I'm keeping all my game state in a ChangeNotifier, including the .stage
which can be "start", "playing", "winning", etc:
final ChangeNotifierProvider<GameState> gameStateProvider =
ChangeNotifierProvider<GameState>((ref) => GameState(stage="start"));
My build()
functions start by accessing this state:
build(context, ref) {
final GameState gameState = ref.watch(gameStateProvider);
And build themselves depending upon the state of gameState
.
But somewhere I have to detect that the game has progressed to the point that I should update state, e.g. gameState.stage = 'winning'
. I tried this:
build(context, ref) {
final GameState gameState = ref.watch(gameStateProvider);
if (gameState.stage == "playing" && gameState.someConditionsAreMet) {
gameState.stage = "winning"; // .stage setter triggers notifyListeners()
}
But I am not allowed to call notifyListeners
in build()
because it tries to mark the widget _needsBuild
during the build phase.
So I wrap the call in a Timer.run()
to make it asynchronous, and the widget happily rebuilds during the next frame:
build(context, ref) {
final GameState gameState = ref.watch(gameStateProvider);
if (gameState.stage == "playing" && gameState.someConditionsAreMet) {
Timer.run(() => gameState.stage = "winning"); // OK: will flag dirty only after build
// now what?!
...but now I don't know what to return from build()
when that if
gets triggered:
- I can't return what I would normally return when
gameState.stage == "winning"
, because it still thinks the stage is"playing"
(theTimer.run()
callback hasn't executed yet.) - I can't return what I would normally return when
gameState.stage == "playing"
, because the conditions that made me want to trigger the change of stage make that impossible (e.g. I print the strongest enemy's HP, but there are now zero enemies.) - I can't return
SizedBox.shrink()
as a hack because that would cause a single frame to render blank, causing a flash on the screen before the.stage = "winning"
widget can render.
I considered just doing the update to gameState.stage
inside gameState
itself, as soon as the right conditions were triggered. But then stateless widgets don't have a way of detecting that state has just changed to do one-time events, like pushing a welcome modal on top of the main screen the first time .stage
changes from "start"
to "playing"
.
So I'm clearly doing something wrong architecturally. How do experienced Riverpod users do it?