Flutter meets Redux: The Redux way of managing Flutter applications state
State management is one of the most important parts of developing any application. If you’re about to build your next application using Flutter, one of your first questions will probably be “how to manage my application’s state?!”
There are a couple of different tools/means to manage your application’s state.
I used to work as a React/React-Native developer for a few years, so I’m familiar with Redux. In this article, I’m going to talk about how I managed to handle my app’s state using Flutter-Redux package.
Redux is built on top of three main concepts:
- Store: The main object that holds your application’s state. (https://redux.js.org/glossary#store)
- Action: A plain object that represents an intention to change the state. (https://redux.js.org/glossary#action)
- Reducer: A function that calculates the next state based on the incoming action. (https://redux.js.org/glossary#reducer)
So how can this whole thing help me to manage my app’s state?
Describing it with a simple example like a todo app. In this app, we’ve got two widgets, a todo list, and an input to add todos.
- Todo List Widget: A widget of all of the todos that will be consuming the todos attached to the store and rebuild itself whenever the list of todos is changed.
- New Todo Input Widget: This widget will dispatch an action to add a new todo to the list of todos which triggers the Todo List Widget rebuild.
That’s all! Let’s dig in with a more real-world example, a blog!
A simple blog app
We’re going to create a simple blog app that uses jsonplaceholder as the REST API.
First, init the project:
flutter create redux_example && cd $_
Then, run the following commands to create the needed directory structure and files:
cd ./libmkdir src && cd $_mkdir models && touch models/i_post.dartmkdir redux && touch store.dartmkdir -p redux/posts && cd $_touch posts_actions.dart && touch posts_reducer.dart && touch posts_state.dartcd ../../../../
Now you should have the following directory structure and files inside the
│ └── i_post.dart
│ ├── posts_actions.dart
│ ├── posts_reducer.dart
│ └── posts_state.dart
Time to add dependencies to
And install them by running:
flutter pub get
First, we’re going to start with
i_post.dart this file has the responsibility to hold our model (API response) shape and parse the JSON for us. I’ve assumed that you’re familiar with basic networking and JSON serialization/deserialization if not so please go take a look at https://flutter.dev/docs/development/data-and-backend/json
Here is our IPost model:
Next, we’re going to create our posts state, a slice of the main state. inside the
PostsStateis an immutable class that has the responsibility to hold our posts state. (line 5)
factoryconstructor will be later used to fill the initial main state. (line 16)
copyWithmethod will be later used to get a copy of our PostsState to update this piece of the main state. (line 22)
Note: A class is immutable if all of the instance fields of the class, whether defined directly or inherited, are
final. (line 4)
Next, we create our posts reducer.
A simple function that receives previous PostsState and an action containing the next PostsState, this is where we use our
copyWith method what it does is that it’s a method that receives all PostsState fields and returns a new PostsState. In other words, create a new PostsState with these params but if a passed parameter is
null, use the value from old PostsState.
Last remaining part of our redux/posts directory,
In Redux we say that actions (reports) are “dispatched” to the store to let it know which things happened. As it turns out, there’s a
.dispatch() method in the store for just this purpose.
There are 2 ways to dispatch actions in Redux,
- Dispatch a simple action (A class containing some payload): line 10–15 posts_actions.dart
- Action Creators: line 17–35 posts_actions.dart
But wait, what exactly is an Action Creator?
An Action Creator is a function that returns an Action, an Async Action Creator is a function that receives the store and can dispatch action later asynchronously. that’s what our fetchPostsAction is. the function that makes the actual API call and dispatches result action when we get our response. Also, it handles our side effects like we’re still loading or an error happened.
In Redux without any additional libraries you can only dispatch actions of type objects but what should I do to dispatch functions or async functions (async action creators)? the solution is to use a small library called Redux Thunk.
What is Redux Thunk?
Redux Thunk is a redux middleware that helps you to dispatch functions as actions, under the hood it does one job, check that if passed action is a function then call it and pass the store. here is a part of the source code actually:
Okay, it’s time to bring them together, in our store.dart we have three main things:
- appReducer: the root reducer of our entire application is where we use all of our reducers, let’s say for example you have the same scenario for auth (holds userProfile, isLoading, isError), we should use userReducer here too.
- AppState: this is our main state object, the one that holds entire applications state. if we would have a piece of state for auth we should add it here too.
- Redux class: this is just a helper class we’ll be using in our main file to bootstrap redux. it has 2 static methods, a getter that we can later use anywhere in our app to access our store and consume it’s data or dispatch actions and an init method that we use to initialize our store, we made this a separate async method so maybe later if we needed to persist our state we can read our initial state from some data source (file, database or network) and initialize our store asynchronously. Actually, there is a library that helps us do this called Redux Persist.
When creating a store you will need three things, your root reducer (appReducer), middlewares ([thunkMiddleware] or an empty array if you don’t plan to use any middlewares) and initial state which is AppState.
This is the last part where we initialize our store and consume data to render our list of posts. but before we jump into our main file we need to talk about Flutter Redux.
Flutter Redux is a library that helps us to connect our application to a Redux store and then either consume data or dispatch actions. it has three widgets inside StoreProvider, StoreBuilder, and StoreConnector. under the hood, it uses a Flutter InheritedWidget, inheritedWidget allows you to inject data through your Flutter widget tree so any widget inside the tree below them can access that data and rebuild themselves whenever that data changes.
Flutter Redux uses inherited widget to inject the store to your Flutter widget tree, if you’re familiar with React, it’s so much like React Context. for more information on inherited widget checkout https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html
In our case we’re using StoreProvider and StoreConnector but what are they?
StoreProviderit passes our Redux Store to our tree of widgets.
StoreConnectorgets the Store from StoreProvider, reads a piece of data from our store and passes that data to its builder function, then whenever that data changes, rebuilds itself.
- In our main function (where Dart programs start) first, we initialize our Redux store (line 8) with the helper Redux class which we wrote previously on store.dart
- In our MaterialApp home, we’re using StoreProvider to inject our Store to the entire tree of our application (line 21)
- In our MyHomePage widget, we have a method called _onFetchPostsPressed (line 39) which we’re using as a callback for whenever the user presses the Fetch Posts Button (line 53)
- Also in our MyHomePage widget, we have three connected widgets which are consuming data from the store with StoreConnector, our loading widget (line 55), Error message widget (line 66) and the actual widget of list of posts (line 78)
Notice how the StoreConnector widget is getting connected to our Store, it has a converter which does that job, it passes our store as a param and we can return which part of our state we want to consume.
converter: (store) => store.state.postsState.posts
When using StoreConnector don’t forget to pass your data types as generic arguments
Here we’re telling our store connector that the AppState is the Shape of our State and we want to consume a list of posts. that’s it, we have an application that uses Redux to manage its state.
Last thing, what is that
distinict parameter that we’re giving it the value of
A word on Performance
As Flutter Redux author says:
Since everything funnels through a Single
Store, any time the State changes, all Widgets listening to the Store (StoreConnectors, etc), will rebuild.
To change this, you can use the
distinctoption on the
StoreConnector. It will listen for changes, but if the ViewModel hasn't changed, it won't trigger a rebuild.
Let’s say you have a page that contains 10 widgets that are connected to your store and consuming data. as Flutter Redux author says, any time the State changes, all Widgets listening to the Store (StoreConnectors, etc), will rebuild. but that’s not the behavior we’re expecting, we want our StoreConnectors to rebuild whenever that special piece of data that they are consuming is changed (ViewModel).
For now, to achieve this we have to pass
true to tell Flutter Redux to do a comparison between the new and old values and rebuild the widget if the value is actually changed. from Flutter Redux source code:
If you’re thinking that this should be the default behavior you’re probably right, there are some open issues that people are discussing to make this as the default behavior.
What it does is that it just performs an equality check between the new and old values.
Dart has good support on doing equality checks between objects you can even override equality check operator for an Object in Dart! note that all types of data are Objects in Dart.
For more information on equality check on more complicated data types rather than simple strings or booleans check out these links:
Demystifying ‘==’ and ‘.hashCode’ in Dart
Today i came across a blog (Defining equality (==) and hashCode for a Dart Class by Patrice Chalin). I asked the…
source is available at https://github.com/bardiarastin/flutter_redux_example
Thanks for reading, please share your thoughts and make sure to follow me on Github.