Cloud Firestore is a great persistence option to keep your data in sync across mobile and web clients. Redux is a great solution to manage your application’s state in a way that’s easier to reason about.

Things get a bit hairy when you try to integrate an external event driven service, like Firestore or Firebase Realtime Database, into the synchronous world of redux. You need to figure out:

how to maintain the connection between Redux and Firestore

where to START listening for data change events

where to STOP listening for those events

In this article I’ll showcase one way of solving this problem. I’m using Flutter as the context, but the ideas I’m presenting could be used in other environments as well, like react/react-native.

What are we doing today?

We’ll make a simple counter app with redux, add Cloud Firestore to it and keep the redux store in sync with data from Firestore.

How are going to do that?

Using redux.dart, flutter_redux and redux_epics.

How exactly? Keep reading… :)

Do you need to know some stuff & things?

Project setup

We’re going to keep it simple and use the default counter app that gets created when you make a new project in Flutter.

So make a new project using either Android Studio, Intellij Idea or the command line with flutter create .

Right now, the app uses a StatefulWidget to save the counter. Let's switch it to redux!

Reduxing our counter

I’ll assume you already know how redux works so I won’t go too much into detail how to implement it for the counter app.

Add redux dependencies to your pubspec.yaml file:

The redux package is a great Dart port by Brian Egan and John Ryan. It’s a plain Dart package, so you can use it for a command line application as well, not only Flutter.

The flutter_redux package (say thanks to Brian!) gives us the glue we need to marry together redux and Flutter.

Implement redux, already!

First, we need to define the AppState that will be saved in the redux store.

We have one action that can change the state: a simple IncrementCounterAction .

The reducer is pretty straightforward.

We can put everything together in main.dart .

The redux implementation is pretty straightforward:

the store is just a final field of MyApp widget;

widget; we use a StoreProvider from the flutter_redux package to expose the store to the widget tree;

from the flutter_redux package to expose the store to the widget tree; down in the widget tree, StoreBuilder gets the AppState and renders the counter;

gets the and renders the counter; when pressed, the + action button dispatches an IncrementCounterAction to the store.

Adding Firebase into the mix

If you need help with Firebase integration, there is a great codelab that does just that. The only difference is that we’ll be using only cloud_firestore for this demo.

Add firestore dependency to your pubspec.yaml file:

Again, what do we want to accomplish?

persist our application state to Firestore;

each time Firestore emits a data change event we want the redux store to get updated with the latest value.

If we do this right, we’ll be able to count clicks from multiple devices and see changes happening in real time.

“Firestore is our real truth!” Amen!

MyHomePage is the widget that gets the counter from the store and renders it. As long as this widget is being displayed to the user we want the redux store to be in sync with Firestore.

Flutter offers a very easy way to init subscriptions when a widget is displayed for the first time and dispose them when the widget is gone: we override initState() and dispose() methods from the State class.

But we no longer have a State<MyHomePage> , you might say...

No worries, both StoreBuilder and StoreConnector have 2 callbacks, onInit and onDispose , that we can use.

From flutter_redux:

Perfect! Now we know when to start and stop the Firestore connection.

Let’s add the actions and dispatch them.

We need another action, let’s call it CounterOnDataEventAction , that will be dispatched each time Firestore fires a data change event for our counter value. This will come in handy in the next section.

Next we need to create a middleware that will watch for our request and cancel actions and manage the connection to Firestore.

Middleware madness with redux_epics & RxDart

redux_epics intro

With redux.dart, a middleware is just a function that receives the store, the dispatched action and a dispatcher (should you choose to let the action flow through).

What redux_epics brings to the table are Dart Streams , and Streams are awesome for event driven systems like Firestore & Firebase Realtime DB.

An Epic is a function that receives a Stream of actions, handles some of those actions and returns a new Stream of actions.

That’s it. Actions go in, actions come out.

The actions you emit from your Epics are dispatched to your store, so writing an Epic that simply returns the original actions Stream will result in an infinite loop.

Do not do this!

epic dependencies!

Ok, now let’s add the redux_epics dependency to pubspec.yaml :

redux_epics comes packed with RxDart.

RxDart gives us a Stream on steroids, called Observable. Observable is a subclass of Stream, so we can use it whenever we need a Stream.

sync. what you came for.

Now everything comes together nicely!

Let’s see what’s happening in counterEpic , step by step:

we create a new Observable from the actions Stream, so we’ll get operators like map() , ofType() and flatMapLatest() ; ofType() operator filters the Observable, letting pass only actions of type RequestCounterDataEventsAction and casts those actions from dynamic to RequestCounterDataEventsAction ; flatMapLatest() (in RxJava2 it's called switchMap ) takes our request action and returns a new Observable that will emit CounterOnDataEventAction s and dispose the previously created Observable;

(for more details about flatMap, switchMap & co) each time a document changes on Firestore, document.snapshots Stream emits a new DocumentSnapshot with those changes make a new Observable out of the snapshots Stream; extract the counter value from the document snapshot; wrap each counter value into a CounterOnDataEventAction that will be dispatched to our store; takeUntil() is where we close the connection to Firestore (remember the snapshots Stream?) when the input actions stream emits a CancelCounterDataEventsAction .

Now we need to update our counterReducer to handle CounterOnDataEventAction .

That’s it! We have an Epic that will keep our redux store in sync with Firestore.

There’s only one thing missing: we can no longer increment our counter!

Let’s fix that with another Epic.

Each time IncrementCounterAction is dispatched, we increment the counter value by 1 and return a new Observable that will emit CounterDataPushedAction if Firestore updated our counter successfully, or CounterOnErrorEventAction if something went wrong.

We don’t really need CounterDataPushedAction for anything, but we should fulfil the Epics contract: actions go in, actions come out, not nulls. :)

I’ll leave it up to you to handle the error action (maybe display a Toast).

wiring up

The only thing left is wiring our middleware to the store.

Both EpicMiddleware and combineEpics are provided by redux_epics.

Wrap-up

This pattern allows us to keep the widgets simple and testable and offers an efficient way to hook-up into an external event driven system like Cloud Firestore, or Firebase Realtime Database.

This was a long read, I know. Thanks for hanging in there!

You can find the entire project on github. Also, please give some love to the awesome packages we used in this article.

Leave your questions and feedback in the comments, I’ll do my best to reply.

Note: If want to use Cloud Firestore in your projects please vote for this pull request and the covered issues. I need those timestamps :).

About us

Shift STUDIO is a development studio that works with great companies to create amazing apps. We have been building native apps for Android and iOS ever since mobile became a thing… but Flutter got as all wet and excited like never before!

If you wanna see more articles like this, follow us on twitter @shiftstudiodevs .

Contact us about your digital project.