Skip to main content

Wait, fail, succeed

Most async processes in your app take some time to finish, and they can either succeed or fail. For example, loading data from a server or saving something to a database. You want to display a spinner while it's running, show the result when it completes, or present an error message if it fails.

These are called "process states":

  • Waiting: The process is currently running.
  • Failed: The process failed with an error.
  • Succeeded: The process succeeded.

In Async Redux, these processes happen when actions are dispatched, which means we need a way to know if an action is currently being processed, if it succeeded, or if it just failed.

Thankfully, this is very easy to do with Async Redux, by using the following methods:

  • isWaiting(actionType): Returns true if the given action type is currently being processed.
  • isFailed(actionType): Returns true if the given action type just failed.
  • exceptionFor(actionType): Returns the exception that caused the action to fail.
  • clearExceptionFor(actionType): Clears the exception that caused the action to fail.

In widgets

In widgets, we have access to those methods by using the built-in extension methods on the context object.

We have already previously seen how to read the state and dispatch actions from widgets:

Widget build(BuildContext context) {
return Column(
children: [
Text('Counter: ${context.state.counter}'),
ElevatedButton(
onPressed: () => context.dispatch(IncrementAction()),
child: Text('Increment'),
],
);

Now, let's see how to show a spinner while an action is being processed, and show an error message.

Show a spinner

Method isWaiting(actionType) returns true if the given action type is currently being processed. By using this method, you can show a spinner while an action is being processed:

Widget build(BuildContext context) {
return context.isWaiting(IncrementAction)
? CircularProgressIndicator(),
: Text('Counter: ${context.state.counter}');
}

Try running the: Show Spinner Example. When you press the "+" button, it dispatches an increment action that takes 2 seconds to finish. Meanwhile, a spinner is shown in the button, and the counter text gets grey.

Show an error message

Method isFailed(actionType) returns true if the given action type just failed. By using this method, you can show an error message when an action fails:

Widget build(BuildContext context) {
if (context.isFailed(IncrementAction)) return Text('Failed');
else return Text('Counter: ${context.state.counter}');
}

If the action failed with a UserException, you can get the exception by doing var exception = context.exceptionFor(actionType) and then get the error message to eventually show it in the UI.

Widget build(BuildContext context) {
if (context.isFailed(IncrementAction)) {
var exception = context.exceptionFor(IncrementAction);
return Text('Failed: ${exception.message}');
}
else return Text('Counter: ${context.state.counter}');
}

Combining isWaiting and isFailed

Suppose we have an async action that gets a list of items from a server. You can show a spinner while the action is being processed, and show an error message if the action fails:

Widget build(BuildContext context) {
if (context.isWaiting(GetItemsAction)) return CircularProgressIndicator();
else if (context.isFailed(GetItemsAction)) return Text('Failed');
else return ListView.builder(
itemCount: context.state.items.length,
itemBuilder: (context, index) => Text(context.state.items[index].name),
);
}

Now let's repeat the previous code, but add a button (in a Column) that retries the action:

Widget build(BuildContext context) {
if (context.isWaiting(GetItemsAction)) return CircularProgressIndicator();
else if (context.isFailed(GetItemsAction)) return Column(
children: [
Text('Failed'),
ElevatedButton(
onPressed: () => context.dispatch(GetItemsAction()),
child: Text('Retry'),
),
],
);
else return ListView.builder(
itemCount: context.state.items.length,
itemBuilder: (context, index) => Text(context.state.items[index].name),
);
}

As soon as the user presses the retry button, the spinner will be shown again, and the error message will be cleared. This happens because the error message is cleared automatically when the action is dispatched again.

You can always clear the error message explicitly by calling context.clearExceptionFor(actionType), but it's not necessary to do so before dispatching the action again.


Now, let's see how Async Redux supports interacting with widgets that manage their own internal state, as well as how to trigger one-time side effects, like opening a dialog or hiding the keyboard.