Flutter: Displaying a toast notification (SnackBar) in Redux flow
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 thecopyWith
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 yourbody:
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.