Disclaimer: This article is written for mobile app developers who have heard of Redux and Flutter framework.
A quick recap on Redux (as opposed to Redux Thunk): Redux helps app developers easily manage the state of the app by organizing the app states and actions to be dispatched in a modular way. In short, it separates the concerns of different parts of our application.
Let’s start with the classic Flutter scaffold app that does one thing — increment a counter!
I will skip the details of how to integrate Redux into our app.
Our app state consists of only one property — counter, which keeps track of the number of times we pressed the increment button.
We only have one action for the time being…
Our store holds our current app state:
In practice, we would put the above codes in separate files. For the purpose of this tutorial, I will place all of the codes above in one file — main.dart.
With Redux, when an app user triggers an event (click the increment button), an action is dispatched. Our reducer processes the action and spits out a new app state, updating our store, which in turn updates our UI. Notice that the entire flow of this state management is synchronous — the action to be dispatched is simply incrementing our current count.
Sweet. Everything works nicely, until we want to dispatch an asynchronous action, for example retrieving something from the database or calling some http request. The nature of this action is async because we don’t want to block the execution of the rest of the codes in our app. We typically would like to show a loading icon in the meantime.
You might ask: Why don’t we just dispatch the async action and then process it in our reducer? The answer is simply because this breaks the contract we made with our reducer. Our reducer is the heart of the functional style of Redux. It accepts a State object and an action as arguments, and spits out a new State object. That’s it. No Future object is returned. We don’t want it to process async code (we’re not allowed to add the async keyword). That’s the beauty of the functional style of Redux.
To process async actions, we use what’s called the Middleware. It sits between the action to be dispatched and our reducer. When we dispatch this async action, it does not get processed by our reducer; Instead, the async action that we’ve just dispatched will be processed by our middleware. We call such action a ThunkAction. The middleware recognizes that the action is ThunkAction because it takes only one argument — a Redux store. It is represented by the following signature:
Note that we need to import redux_thunk package.
redux_thunk | Dart Package
redux_thunk Dart package - Redux Middleware for handling functions as actions
With the middleware comes into play, our redux architecture looks like this on a high level:
Our middleware intercepts our call to dispatch an async action — a ThunkAction. Inside this thunk action, we can optionally dispatch a synchronous action which will get processed by our reducer.
Let’s extend our current app to generate a random quote. We will have two new fields in our app state:
We have to update our store to include the ThunkMiddleware:
Say I want to dispatch a getRandomQuoteAction (you know what this action does). This action is a thunk action because it asynchronously makes an http request. In the response, we are interested in the quote we received and its author. We then want to update our app state using this new quote and author that we received by dispatching UpdateQuoteAction object (On line 13, we use the store argument to dispatch). Our thunk action looks like this:
Note: Remember to import ‘package:http/http.dart’ as http; and import ‘dart:convert’;
We now have two synchronous actions (processed by our reducer), IncrementAction and UpdateQuoteAction:
Besides IncrementAction, our reducer will need to account for UpdateQuoteAction:
When a user click the ‘increment’ button, a synchronous action (IncrementAction) is dispatched. This action is processed by our reducer which in turn churns out a new app state with counter increased by one. When the user click the ‘generate random quote’ button, an async action/thunk action (getRandomQuote) is dispatched. Since it is a thunk action, rather than being processed by our reducer, the middleware intercepts the action and processes it. After we received an http response, we dispatch a sync action (UpdateQuoteAction) which is processed by our reducer. (Note that we can choose not to dispatch anything inside our ThunkAction.)
That’s all there is to Redux Thunk. If there is one thing to take away from this article, it will be that Redux Thunk helps separate our async code from our reducer, resulting in a more maintainable app.
The source code for this project is in my github repo:
The Flutter Pub is a medium publication to bring you the latest and amazing resources such as articles, videos, codes, podcasts etc. about this great technology to teach you how to build beautiful apps with it. You can find us on Facebook, Twitter, and Medium or learn more about us here. We’d love to connect! And if you are a writer interested in writing for us, then you can do so through these guidelines.