Before and after the reducer
Suppose you want to prevent the user from touching the screen, while MyAction
is running.
This means adding a modal barrier before the action starts, and removing it after the action ends.
It's indeed common to have some side effects before and after the reducer runs.
To help you with these use cases, you may override your action methods before()
and after()
, which run respectively before and after the reducer.
Note: implementing the
reduce()
method is mandatory, butbefore()
andafter()
are optional. Their default implementation simply does nothing.
Before
The before()
method runs before the reducer.
To run synchronously, return void
. To run it asynchronously, return Future<void>
:
// Sync
void before() { ... }
// Async
Future<void> before() async { ... }
What happens if method before()
throws an error? In this case, the reduce()
method will NOT run.
This means you can use before()
to check any preconditions,
and maybe throw an error to prevent the reducer from running. For example:
// Shows a dialog if there is no internet connection,
// and prevents the reducer from running.
Future<void> before() async {
if (!await hasInternetConnection())
throw UserException('No internet connection');
}
Note: If method
before()
returns a future, then the action is also async (will complete in a later microtask), regardless of thereduce()
method being sync or not.
After
The after()
method runs after the reducer.
It's important to note the after()
method is akin to a finally block,
since it will always run, even if an error was thrown by before()
or reduce()
.
This is important so that it can undo any side effects that were done in before()
,
even if there was an error later in the reducer.
Note: Make sure your
after()
method doesn't throw an error. If it does, the error will be thrown asynchronously (after the "asynchronous gap") so that it doesn't interfere with the action, but still shows up in the console.
Example
In our model barrier example described above, we could dispatch an action to turn on a modal barrier on and off.
Suppose we define a BarrierAction
:
class BarrierAction extends AppAction {
final bool hasBarrier;
BarrierAction(this.hasBarrier);
AppState reduce() => state.copy(hasBarrier: hasBarrier);
}
And then your widget tree contains a modal barrier,
which is shown only when hasBarrier
is true:
return context.state.hasBarrier
? ModalBarrier()
: Container();
After this is set up, you may use before()
and after()
to dispatch the BarrierAction
:
class MyAction extends AppAction {
Future<AppState> reduce() async {
String description = await read(Uri.http("numbersapi.com","${state.counter}");
return state.copy(description: description);
}
void before() => dispatch(BarrierAction(true));
void after() => dispatch(BarrierAction(false));
}
The above BarrierAction
is demonstrated
in
this example.
Creating a Mixin
You may also create a mixin to make it easier to add this behavior to multiple actions:
mixin Barrier on AppAction {
void before() => dispatch(BarrierAction(true));
void after() => dispatch(BarrierAction(false));
}
Which allows you to write with Barrier
:
class MyAction extends AppAction with Barrier {
Future<AppState> reduce() async {
String description = await read(Uri.http("numbersapi.com","${state.counter}");
return state.copy(description: description);
}
}
Try running the: Before and After Example.