Prerequisites

This article assumes that you’ve either recently got acquainted with how Redux can be utilized in a Flutter project or you’re on this way. If you don’t have enough confidence in Redux terms, I’d suggest reading the docs in redux.dart and flutter_redux repositories.

Motivation

As soon I learned the basic samples of Flutter + Redux and started applying the concepts to a simple app I realized that such operation as “displaying a SnackBar notification” was not obvious to implement in Redux flow.

Execution of such one-off operations is relatively hard in Redux as you only get to observe “current state”, meaning an action must somehow emit a state for “start_displaying_toast” and “stop_displaying_toast” to have the effect akin to calling view.showToast() .

Goal

Let’s create an app that loads a random famous quote from some API and displays it. The app will display a quote and a button to get a new quote. During the process of getting the data a loading indicator should be shown and, if any error occurs in the meantime, a toast notification describing the error should arise.

Development

Make sure that flutter_redux and http packages are listed in your pubspec.yaml file and installed.

Model

This is a small class that is used to hold a quote’s data.

State

In Redux, all the application state is stored as a single object. It’s a good idea to think of its shape before writing any code.

For this app we need to store three different pieces:

The quote that is currently displayed.

The flag indicating that the data is being loaded and that the progress indicator needs to be shown.

The error which has occurred and is used to display a toast notification with the corresponding message.

Please note that AppState is an immutable object. Every time you need to change a state you use the copyWith method which returns a modified copy of the state. The state lives at the top of your Widget hierarchy within a Store .

Actions

Actions are payloads of information that send data from your application to your store. They are the only source of information for the store. You send them to the store using store.dispatch() . This ensures that neither the views nor the network callbacks will ever write directly to the state. Instead, they express an intent to transform the state. Because all changes are centralized and happen one by one in a strict order, there are no subtle race conditions to watch out for.

LoadQuoteAction is used to trigger the loading process, QuoteLoadedAction delivers the notion of the finished process and the quote itself, ErrorOccurredAction is an indicator of an error and ErrorHandledAction is used as a trigger to transform the app’s state back to its usual form.

Reducers

Actions describe the fact that something has happened, but don’t specify how the application’s state changes in response. This is the job of reducers. The reducer is a pure function that takes the previous state and an action, and returns a new state.

The reducers have to stay pure. You should never mutate the arguments inside a reducer or perform side effect operations like API or database calls. This is the job for Middleware.

Middleware

Middleware are special functions that run before your dispatched actions reach your reducer. They can be used to listen for different actions and perform async calls, such as talking to a web server. Once they get a response from the web server, they can dispatch our “success” or “failure“ actions.

Presentation

Take a look at the HomeScreen widget that I came up with. This is not a pure “Presentation” widget. This is a combination of the so-called “StoreConnector” and “Presentation” widgets. This HomeScreen widget is responsible both for converting the latest AppState to a _ViewModel and displaying UI in accordance with the current state.

Let’s take a closer look at line 20 in the code above. ErrorNotifier is the piece of logic of particular interest here.

ErrorNotifier is a receiver widget. Essentially it is a widget that doesn’t display anything, but is set up to react to changes in the store to do something within the build tree. You wrap your receiver around your body: property on all your scaffolds and you'll be able to receive your SnackBar messages from anywhere you can dispatch an action. The only downside is the use of temporary data in your store.

So, as you can see ErrorNotifier tracks the updates of store.state.error and shows a SnackBar when there is an error. Also, note that it dispatches ErrorHandledAction before showing the toast notification, which will be handled by the corresponding reducer to clean the error from the AppState .

And here is main.dart file which combines all the components described above.

Demo

Play the video to see how the app looks like.

Feel free to grab to the source code of the app and give it a try.

Sources: