Caching data with Flutter Bloc

Thomas Gallinari
5 min readJul 24, 2020

Hello and welcome to this new article of the Journey to Flutter series!

At Intent Technologies, we deal with a lot of data, like… A LOT! Actually data management is our core business so nothing surprising here. And our users need to access this data seamlessly — i.e. with minimal loading times.

That is where caching comes in. However there are plenty of ways to cache data, so here is my take on the subject.

About Flutter Bloc

Flutter Bloc is a library for state management, based on the BLoC pattern introduced by Google, and this is the one we use at Intent Technologies. I will not go deep into it here, there are a lot of very good articles about Bloc, beginning with their official documentation.

As a reminder, here is a simple diagram that sums it up:

I will focus on the Bloc ↔ Data communication. In our application, our Blocs get data from Repositories which in turn get data from the API.

Here is an example with a MenuBloc where we get some information about the authenticated account from an AccountRepository:

Now as you can imagine, each time we open our menu we will have to wait for the API to respond before we can display the user name. Which is a shame, even more when the data is unlikely to change within a session (as it is the case with a user account).

Caching solutions

Basically we have three solutions for caching data:

  • in the Bloc
  • in the Repository
  • in the API

Caching in Bloc

Pros:

  • there are packages for that (see hydrated_bloc)
  • you save the Bloc state, i.e. the Widget’s UI, not only its data

Cons:

  • if multiple Blocs use the same data, each one will fetch the data then cache it

Caching in Repository

Pros:

  • you save the data, thus you load it once then it can be accessed from cache by any Bloc
  • after all, the job of a Repository is to fetch the data from the right source depending on the situation, so it seems appropriate for caching

Cons:

  • you do not save the Bloc state, so it will be recomputed the next time it is created (which should not be an issue in Flutter)

Caching in API

Pros:

  • this is done server-side, so you have nothing to do

Cons:

  • it still depends on the network so the loading time will be much longer than if you read a local cache

Well, as often there is no good or bad answers, it all depends on your needs. But in this article I will explain the Repository solution, that I chose to implement in our Intent Technologies application.

Cache vs API

Remember our example above? We will modify it slightly so the Repository will either load the data from the API, or return it from the cache if available:

The changes are highlighted in red. Now our Repository stores the latest Account in a local variable, and returns it instead of calling the API.

This should be enough for an Account which, again, should not change within the time the user plays with the application.

But what about more short-lived data? We would like to read them from the cache to instantaneously display information to the user, but at the same time this data may have changed so we would like to refresh it from the API and update it on the screen.

To sum up, we need to return a stream of two values… Wait, did I say “Stream”?

Streams to the rescue

If you go back to our MenuBloc, you will notice the its mapEventToState function returns a Stream of States. This is because it can return multiple States for a single event. For example, when it receives an event for loading data, it can first return a LoadingState, then load the data and return it in a ResultState (or ErrorState if something goes wrong). This can be achieved with a Stream. You can picture a Stream as a pipe where data flows from an end and you listen to it at the other end.

So how can it help for caching? Well, as we said, we would like to immediately return the cached data if available, then refresh it from the API and return the new value if it changed.

Now instead of returning a Future, our Repository returns a Stream, and because of this it is annotated with async* instead of async.

As you can see, we do not return a singleAccount anymore, we yield, meaning that we possibly publish several values to the Stream.

The Bloc needs adjustments as well:

We now map all the values returned by the Stream to a MenuState, which we yield* (notice the * because we yield multiple values) to the consumer. In other terms, the Bloc will be able to pass both our cached Account (immediately), then the refreshed Account from the API, to the UI.

This is a pretty simple and elegant solution, however our example may lack a few features, like cache expiry, or the ability to fetch the data from cache only or API only… But now the pattern is implemented, it should not be too difficult to add more features.

Please let me know what you think, and if you have come with other solutions to cache your data in Flutter!

--

--