Redux in Flutter: demystified.

Redux is one of the many state management solutions available for Flutter. It originated from Javascript with the means to make a predictable state container for apps. However it is still quite popular I noticed that developers consider redux to be complicated and also do not like the boiler plate code. I personally agree that redux tend to be a bit more complex than for example BLOC and there is some boilerplate but there are also advantages:

  • It has proven to be very scalable.
  • Clear separation of responsibilities.
  • One direction of flow which makes it easy to undo a state change or debug your app.
  • State is updated by pure functions without any side effects.

In this article I hope to clarify the foundation of redux and show how it is implemented in Dart. Later I will show how to handle side effects and last but not least I show two ways of creating a reactive UI based on the ReduxStore. It is not my intention to convince someone to use Redux over other state management solutions, but hopefully I will make it easier to understand.

The three core principles

As I said before Redux originated from Javascript and consists of three core principles:

  • The global state of the app is stored in an object tree within a single store.
  • State is read only and the only way to change the state is through dispatching an action.
  • Changes to the state are made with pure functions (a.k.a reducers).

Global state of the app is stored in an object tree within a single store.

App state is nothing more than a data class that can consist of other classes.

The advantage of the fact that the app state is stored in one global object is that at every moment the current app state is available through the store. This can be handy for debugging or for persisting the app state in a database. I will explain how to create a store object later.

State is read only.

The state cannot be directly mutated by a view or backend client. To mutate the state an action needs to be dispatched. An action is a simple object like shown below.

An action can be seen as a declaration of an intention to update the AppState. To declare this intention the function dispatch is invoked, for example: store.dispatch(ChangeFoobarAction(FooBar.bar)); . Declaring these actions is what many developers consider to be boilerplate but there are also advantages to this approach:

  • Because dispatching intentions is the only way to change the state it becomes easy to replay a certain issue. Also it gives the ability to undo a certain action.
  • Clear separation of responsibilities. The store is the only one responsible for changing the state. This gives us a unidirectional flow of data and events.

Change to the state are made by pure functions: reducers.

A reducer is a pure function that takes the previous state (or a part of it), an action and returns the new state. At some point there will be a need to split the reducer into multiple reducers, because else the file becomes too large. How to do this is explained in the official docs of Redux for dart.

In order to use the store object in the project, the redux package needs to be added as a dependency. Based on the AppState the store can be created, a reducer is mandatory and because I do not like null I also define an initial state.

Summary

Bellow I explain how the app state is modified:

  1. An intention to modify the app state is declared by calling store.dispatch(ChangeFoobarAction(FooBar.bar));
  2. The store calls the foobarReducer and provides the current state and the ChangeFoobarAction to the reducer.
  3. The reducer returns a new AppStateobject.
  4. The store updates the app state and will notify all listeners that are listening to the onchange method. This method is what we need later when we want to build a reactive UI using provider.

This section heavily relies on the official docs of Redux dart and Redux for JavaScript I recommend both sources for more in depth information about the core concepts.

Handling side effects

It is almost impossible to avoid having side effects in an app since normally an app interacts with an API, database or an external device using Bluetooth. In this section I describe how to handle side effects in redux.

Middleware to rescue

A middleware is a function that is executed before the action reaches the reducer, the function returns void and has store, action, Nextdispatcher as arguments. This function comes in handy when changing the app state based on an asynchronous call to an API. The code below shows a basic implementation of the middleware. I decided to split the creation of the middleware and handling the side effects because this improves testability.

The last thing that needs to be done is adding the middleware to the store, so the store knows that it first needs to call the middleware before sending the action to the reducer.

Summary

Let’s see how changing the state, by calling an asynchronous operation in the middleware, looks like from the perspective of the store:

  1. A FetchStatusAction is dispatched to the store by calling store.dispatch(FetchStatusAction());
  2. The store calls the middleware.
  3. The middleware calls the api and then calls next. This is the signal for the store to call the next middleware in line or the reducer.
  4. The foobarReducer retrieves the FetchStatusAction and will return the current app state to the store.
  5. The store updates the app state and will notify all listeners that are listening to the onchange method.
  6. The API completes and will dispatch ChangeFoobarAction(FooBar.bar).
  7. The action will reach foobarReducer and the appstate will be updated.
  8. The store updates the app state and will notify all listeners that are listening to the onchange method.

For more information about async operations checkout the Redux JS docs.

Creating a reactive UI in Flutter

So far pure dart is used and this code will also work in other dart applications. The only thing that needs to be done is creating a reactive UI in Flutter that rebuilds when the app state is changed. I will use two popular approaches that combine very well with Redux: flutter_redux and provider . Both are responsible for creating the view model and rebuilding the following screen:

Flutter_redux

The flutter redux package provides a couple of utilities that makes it easy to consume a redux store and rebuild widgets based on changes in the app state of the store.

The first thing that needs to be created is a StoreProvider. A StoreProvider is a descendant of InheritedWidget and provides a redux store to its descendants. The consumers are usually either aStoreConnector or a StoreBuilder . The example below shows how to use flutter_redux .

Provider

The provider package is a wrapper around InheritedWidget and reduces the boiler plate code, does lazy loading of the data and has a simplified allocation/disposal of resources. In short: it using InheritedWidget easier. The advantage of this package over flutter_redux is that Provider can be used in a broader scope and we can Provide other objects besides only Redux components.

In the example below you see that two providers are created: one for dispatching an action and one for getting FooBar . Again the providers are put on top of the widget tree so there is a guarantee that all the widgets in the app can access the data. Lower in the widget tree a Selector is defined that accesses the data and creates the ViewModel for the screen.

Wrapping up

In this article I explained the core concepts of Redux and how to handle side effects. Furthermore I showed two examples of how the store can be accessed in a Flutter widget.

For further study I recommend the following sources:

Besides being a driven and passionate app developer, I am a keen sportsman and love: cycling, fitness and speed skating.