engineering

Introduction to Redux in Flutter

I do Android and other things @Novoda

Redux is a unidirectional data flow architecture that makes it easy to develop, maintain and test applications. In this post I'll explain how you can start writing mobile apps with Flutter using the Redux architecture.


It was only last month that Google announced Flutter graduated to the beta stage and yet since then the interest for this framework has grown very rapidly.

Flutter is a really interesting piece of technology that can prove very useful in many situations both for indie developers as well as software companies. It is currently being used by Google which makes for a promising future. Flutter allows for very quick iterations, it's developer-friendly and it's multi-platform. Let’s walk through the high level architecture of Flutter and then move onto Redux.

Flutter Widgets

In Flutter, every UI component is a Widget, you compose your whole application UI with Widgets that contain other Widgets, even your application class is a Widget.

Widgets can either be a StatelessWidget or a StatefulWidget:

Flutter StatelessWidget

StatelessWidget is a very simple Widget that doesn't have any mutable state, therefore it needs to be recreated with different parameters in order to display different data.

An example of a StatelessWidget could be a row in a to-do list: this Widget would get the to-do text and the “done” flag as constructor parameters and then display them. Changing the to-do text, or the done flag, requires you to create another StatelessWidget.

Flutter StatefulWidget

StatefulWidget is useful when building a UI based on some mutable state. In this case the Widget gets recreated every time the state is mutated, therefore reflecting the state change in its Widget tree. The StatefulWidget has to create State objects that will hold the mutable state, in addition to creating the Widget tree that StatelessWidgets also have.

An example of a StatefulWidget would be the container for the to-do list items: this container list would extend StatefulWidget and it would create a ToDoState. This ToDoState is where the list of to-dos would live and where we'd create the Widget tree (i.e. ListView). Once there's a user action (when a to-do gets added, removed, etc.) then we'd update the list using the setState in the State object, which would rebuild the Widget tree, showing the added (or removed) item.

This makes for a nice separation of things that change vs. things that do not. But it also has its downsides:

  • When there's state that has to be shared in multiple pages, it needs to sit in the app Widget and then it has to be passed down to each screen's Widget tree, requiring boilerplate code.
  • Multiple Widgets become tightly coupled when there's a user action that has to modify the shared state because the actions have to be communicated up in the Widget tree.
  • Tightly coupled Widgets aren’t reusable and it can be hard to modify the Widget tree if you're planning to make a UI change.

To counteract and avoid some of these downsides, we can turn to Redux.

Redux

Redux is an architecture with a unidirectional data flow that makes it easy to develop applications that are easy to test and maintain.

redux overview: actions get processed with a reducer, which updates the store, that is used to refresh the UI, which may also issue actions

In Redux there's a Store which holds a State object that represents the state of the whole application. Every application event (either from the user or external) is represented as an Action that gets dispatched to a Reducer function. This Reducer updates the Store with a new State depending on what Action it receives. And whenever a new State is pushed through the Store the View is recreated to reflect the changes.

With Redux most components are decoupled, making UI changes very easy to make. In addition, the only business logic sits in the Reducer functions. A Reducer is a function that takes an Action and the current application State and it returns a new State object, therefore it is straightforward to test because we can write a unit test that sets up an initial State and checks that the Reducer returns the new and modified State.

Redux Middleware

The above at first glance appears to be straightforward, but what happens when the application has to perform some asynchronous operation, such as loading data from an external API? This is why people came up with a new component known as the Middleware.

Middleware is a component that may process an Action before it reaches the Reducer. It receives the current application State and the Action that got dispatched, and it can run some code (usually a side effect), such as communicating with a 3rd-party API or data source. Finally, the Middleware may decide to dispatch the original Action, to dispatch a different one, or to do nothing more. You can learn more about Middleware here.

With the Middleware, the above diagram would look like this:

the middleware now sits between the action and the reducer

Redux in Flutter

Taking all this to Flutter, there's two very useful packages we can use, making it really easy and convenient to implement Redux in a Flutter app:

  • redux: the redux package adds all the necessary components to use Redux in Dart, that is, the Store, the Reducer and the Middleware.
  • flutter_redux: this is a Flutter-specific package which provides additional components on top of the redux library which are useful for implementing Redux in Flutter, such as: StoreProvider (the base Widget for the app that will be used to provide the Store to all the Widgets that need it), StoreBuilder (a Widget that receives the Store from the StoreProvider) and StoreConnector (a very useful Widget that can be used instead of the StoreBuilder as you can convert the Store into a ViewModel to build the Widget tree and whenever the State in the Store is modified, the StoreConnector will get rebuilt).

Show me the code

I’ve created a basic to-do list app to demonstrate the concepts discussed above. Let’s go through the important parts.

First, the main.dart file (which is our app’s entry point) defines the application Store object from an initial State, a Reducer function and the Middleware. It then it wraps the MaterialApp object with a StoreProvider that takes the Store and can pass it to its descendant Widgets that need one:

void main() => runApp(ToDoListApp());

class ToDoListApp extends StatelessWidget {
  final Store<AppState> store = Store<AppState>(
    appReducer, /* Function defined in the reducers file */
    initialState: AppState.initial(),
    middleware: createStoreMiddleware(),
  );

  @override
  Widget build(BuildContext context) => StoreProvider(
        store: this.store,
        child: MaterialApp(
          // Omitting some boilerplate here
          home: ToDoListPage(title: 'Flutter Demo Home Page'),
        ),
      );
}

The AppState class contains the list of to-do items and a field to decide whether or not to display the TextField to add a new item:

class AppState {
  final List<ToDoItem> toDos;
  final ListState listState;

  AppState(this.toDos, this.listState);

  factory AppState.initial() => AppState(List.unmodifiable([]), ListState.listOnly);
}

enum ListState {
  listOnly, listWithNewItem
}

In order to display the to-do list, we define a ViewModel class that contains a view-specific representation of the data we need to display, as well as the actions the user can do. This ViewModel gets created from the Store:

class _ViewModel {
  final String pageTitle;
  final List<_ItemViewModel> items;
  final Function onNewItem;
  final String newItemToolTip;

  _ViewModel(this.pageTitle, this.items, this.onNewItem, this.newItemToolTip, this.newItemIcon);

  factory _ViewModel.create(Store<AppState> store) {
    List<_ItemViewModel> items = store.state.toDos
        .map((ToDoItem item) => /* Omitting some boilerplate here */)
        .toList();

    return _ViewModel('To Do', items, () => store.dispatch(DisplayListWithNewItemAction()), 'Add new to-do item', Icons.add);
  }
}

Now we can use the ViewModel class to display the to-do list. Notice that we wrap our Widgets inside a StoreConnector which allows us to create the ViewModel from the Store and build our UI using the ViewModel:

class ToDoListPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) => StoreConnector<AppState, _ViewModel>(
        converter: (Store<AppState> store) => _ViewModel.create(store),
        builder: (BuildContext context, _ViewModel viewModel) => Scaffold(
              appBar: AppBar(
                title: Text(viewModel.pageTitle),
              ),
              body: ListView(children: viewModel.items.map((_ItemViewModel item) => _createWidget(item)).toList()),
              floatingActionButton: FloatingActionButton(
                onPressed: viewModel.onNewItem,
                tooltip: viewModel.newItemToolTip,
                child: Icon(viewModel.newItemIcon),
              ),
            ),
      );
}

In the code above we define that when the user presses the ‘Add’ button, we’ll dispatch an action of type DisplayListWithNewItemAction which indicates we need to modify the application state so that we display the TextField that will let the user create a new to-do item. The action class is defined as:

class DisplayListWithNewItemAction {}

And here’s the Reducer that for this action:

AppState appReducer(AppState state, action) => AppState(toDoListReducer(state.toDos, action), listStateReducer(state.listState, action));

final Reducer<List<ToDoItem>> toDoListReducer = // Boilerplate ignored
final Reducer<ListState> listStateReducer = combineReducers<ListState>([
  TypedReducer<ListState, DisplayListOnlyAction>(_displayListOnly),
  TypedReducer<ListState, DisplayListWithNewItemAction>(_displayListWithNewItem),
]);

ListState _displayListOnly(ListState listState, DisplayListOnlyAction action) => ListState.listOnly;

ListState _displayListWithNewItem(ListState listState, DisplayListWithNewItemAction action) => ListState.listWithNewItem;

This is a simple example but demonstrates the concepts explained above. The full source code for this to-do list app can be found in Github


To conclude, using this architecture in Flutter apps keeps all the concerns well-defined and separate from each other. However, having only tried this in pet projects (due to Flutter still being beta), I'd like to see if it'd scale well in a large projects, as having one State for the whole app makes me think you'd end up composing this State from smaller State objects.

Further refactorings for using Redux at scale, could include organising the Redux components in subfolders by feature (/home, /settings, etc.), even though most examples I've seen use subfolders for each component (/middleware, /actions, etc.).

Last but not least I'd really like to try and build a simple web front-end using AngularDart or even add desktop support with Redux, to see how much code can be reused between the mobile client, the website and the desktop client.

Thanks for following along. If you have any comments, suggestions or want to discuss then talk to me on Twitter 💬

Enjoyed this article? There's more...

We send out a small, valuable newsletter with the best stories, app design & development resources every month.

No spam, no giving your data away, unsubscribe anytime.

About Novoda

We plan, design, and develop the world’s most desirable software products. Our team’s expertise helps brands like Sony, Motorola, Tesco, Channel4, BBC, and News Corp build fully customized Android devices or simply make their mobile experiences the best on the market. Since 2008, our full in-house teams work from London, Liverpool, Berlin, Barcelona, and NYC.

Let’s get in contact