Reduxing Flutter Firebase App – WeightTracker 4
In this post I’m going to go through how I added flutter_redux library to my existing Firebase-connected app. The presented development is part of my Weight Tracker app (simple app for tracking weight) and will be focused only on that.
Note: This is only my approach to this problem. There are probably better ones, if you have any idea how it could be improved, leave a comment.
Introduction
Some time ago I came across Redux library which provides a way to have one state object (not in the meaning of Flutter state) in the application and pass it to widgets in build functions. I recommend going to example to make sure you understand the concept. I didn’t think that I need this kind of tool until I started playing with Firebase Auth and user switching. For me having Database reference in a view class and making sure that it corresponds to the right user and has attached right listeners didn’t work out. Especially having in mind that soon I want to add more Widgets. So I decided to make use of Redux and see if it solves my problems.
Note: I mentioned user switching but it won’t be in the content of this post since it is still in development.
. . .
Getting started
Dependencies
First of all, I added Redux dependencies in your pubspec.yaml (as on 05-10-2017):
State and reducer
Next step is to create state class which will represent the whole state in my application and stateReducer
method for manipulating state. At this point, I will have only the list of weights. So created redux_core.dart
file looks like this:
Interesting thing here is that action
is dynamic. Since there will be different types of actions that will provide various types of data, Brian Egan suggested to have a class for every action and don’t play with generic Action class. And that’s what I did
What is also worth mentioning is that ReduxState
class is immutable. That means that in order to “change” state, we have to create a new one. That’s why I created copyWith
method, so it will be easy to copy state with only changed fields.
Store and StoreProvider
In main.dart
I create a Store
object and provide it in StoreProvider
. From now on every StoreConnector
will have access to the store and therefore to the state.
StoreConnector
Next step is to create ViewModel class in my screen widget. At this moment we don’t really need it but it will be helpful in the future.
Now I will wrap my screen Widget in StoreConnector. Let’s see how it looks like:
We can see that the main Widget is now StoreConnector
. It contains two components: converter
and builder
. A converter is a method that accepts Store
and returns ViewModel
specified in StoreConnector
. At this point theoretically, I could use List as ViewModel since I am not using anything else but it will change soon. Another component of StoreConnector is builder, which is also a method. It is very similar to override build
method except it has also earlier defined ViewModel as a parameter. We can use that ViewModel to populate data into our view.
After changing ListView.Builder
to use data from viewModel we have perfectly working ListView with data coming from our state. The only problem is we have no entries there. Let’s add some!
. . .
Adding action
So far our reducer
method, which is our way to mutate state, is empty. In order to make it work, we need to define Actions
, which we can consider as a contract of how the state can be changed. So let’s introduce adding action:
Simple like that! Now we only need to call it. First, I will add new field in ViewModel class:
Then we need to update build method:
There are a few things worth mentioning here:
- To perform an action we call the dispatch method of Store object with Action we would like to use. This will cause the reducer method to be invoked and do what we want to do.
- We can pass our method accepting entry further and call it exactly when we want it.
- This solution shows us very clearly what data our screen widget uses and what actions it can perform.
Now let’s introduce Firebase Database to the equation.
. . .
Reduxing Firebase Database
Signing in
In my scenario, I want the user to be anonymously logged in as soon as app launches. To do that we will need to do the following changes:
Add new actions
Updated ReduxState
Since the reducer is not supposed to do any API calls, we will introduce Middleware. Middleware is a function that is invoked before reducer in order to call async functions, so that reducer will perform only pure functions without any side effects. To understand this concept better, I recommend reading this article.
What happens here is if we try to dispatch InitAction, then middleware function will try to get the current user and if there is none it will try to signInAnonymously. What’s important is that this is done async, so the reducer will be invoked anyway (thanks to calling next method). When the user is provided, we dispatch new UserLoadedAction with obtained FirebaseUser.
Then let’s update stateReducer method
And call it from widget:
Notice that we added middleware to the Store initialization. From now on, on every launch user will be signed in anonymously. It’s not much unless we will also connect to the database.
Syncing with Firebase Database
Don’t get discouraged if you don’t understand everything while reading. At the end I will explain it and all should be clear.
At first, let’s add DatabaseReference to the state.
Then we will add two new actions:
Next we need to update middleware
And update reducer
How does it work?
- First, we invoke InitAction, in the middleware, we obtain a FirebaseUser and invoke OnUserLoadedAction.
- After the user is updated, we create DatabaseReference and pass it in AddDatabaseReferenceAction.
- This reference has also added OnChildAdded listener which, when called, will invoke OnAddedAction.
- When that one gets passed, it adds an Entry to the state’s list (exactly how AddEntryAction worked before).
And when it comes to AddEntryAction, instead of adding an Entry to list, we simply call Database’s push method. Since now this action won’t even be handled in reducer, because it doesn’t affect the state at all.
If the process is still not clear for you, the following UML sequence diagram may be helpful to fully understand the flow:
For better understanding I also recommend checking out whole source code (linked at the bottom).
. . .
Wrapping up
Now we have reduxed firebase app. I think that having screen Widgets with clearly defined data and actions they will use (in viewModel) and not having any logic in those widgets are very beneficial for code readability. I encourage you to try Reduxing your Flutter app, it really provides great control over the whole application.
That’s it!
You can find the full code here.
Special thanks to Brain Egan (@brianegan) for support
Marcin Szałek
Founder of Fidev
Flutter enthusiast since Alpha release in 2017. Believes that sharing is caring, which lead him to start a technical blog dedicated fo Flutter in its early days. Loves to see beautiful designs become real apps and is willing to help make it happen. Enjoys sunny beaches far from home.
Join the newsletter!
Join the newsletter to keep track with latest posts and get my special Widget to animate views' entrances without any hassle for FREE!
Check out other posts!
Stripe Checkout in Flutter Web
Flutter Web is getting more mature every day. If you want to accept payments using Stripe Checkout in your Flutter web application, this article is just for you!
Stripe Checkout in mobile Flutter app
Have you ever struggled to integrate card payments into your mobile Flutter app? If so, today is your lucky day! In this post, I present how to use Stripe Checkout in the Flutter app without any hassle!
Interacting with Widgets using Framy
Have you ever developed a widget or a page and you wanted to make sure it works correctly in different scenarios but then it turned out that you can’t just reproduce all the cases you want to cover? Framy may solve such problems!
Hi Marcin
Thanks for sharing your adventure, really practical and point to point, it helped me a lot in adopting Flutter, if you get a chance can you also show some love how to best implement Rx dart in similar example?
Kind regards
Hey, I’m glad you like it 🙂
To be honest I had no plans for working with Rx dart but if I don’t have idea what to do I might try and share it 🙂
Hey, thanks the great article. I’m also using firebase, and would like to test middleware, if you have time, could you please write some examples of how this can be done?
Thanks again 🙂
By test you mean try it out or write unit tests? 🙂
I would like to test the middleware that fetching data from Firebase, and not sure how to do that.
You never cancel your subscriptions that dispatch OnAddedAction, OnChangedAction etc. Isn’t that bad practice in a real app?
Thanks for the article!
Hi Tudor!
In my particular scenario I think it is good, because I want to listen to those streams all the time.
In other cases you are right. If I stop needing a subscription I should cancel it.
Now I did a bit of research and I couldn’t find any way to cancel subscription except setting cancelOnError flag while subscribing. Do you know how to do that? 🙂
Hey all! One way to do this would be to create an Epic with the `redux_epics` package. You can then use RxDart and the `takeUntil` operator. Example with a Search api here, but the principles are the same: https://github.com/brianegan/dart_redux_epics#cancellation
I just finished a working prototype with plain redux.dart. I’ll try and write a blog-post about it sometime next week.
The gist of it:
– in widget’s onInit / onDispose dispatch RequestDataEventsAction / CancelDataEventsAction
– in middleware you subscribe to firebase, dispatch DataEventsRequestedAction with the StreamSubscription obj to be saved in your store and OnDataAction each time there’s a new value.
– in your reducer you save the subscription from DataEventsRequestedAction and ?.cancel() + delete it when CancelDataEventsAction.
here’s an actual gist https://gist.github.com/lucamtudor/0cc6a4d101f1ae8ac08e8815104beb81
Wow! Thanks Brian! I will definitely use this!
I would use a more OO approach. The if statement inside a function could get a weird spaghetti code chunk. I would change action to follow GoF command in action and this method will be specialized for each action. Simpler, no ifs and organized.
Yeah, you’re probably right 🙂
Have you tried BLoC pattern? What do you think of it?
Hi, yes I do use it and I really enjoy it, however, I don’t feel competent enough to talk about it yet 🙂
Hello, very interesting, But doing it this way aren’t we using Firebase as a REST API instead of a Real Time Database?