Lazy Loading Data From Firestore in Real Time Using Flutter
Paginate through Firestore, and receive updates in real time
Fetching data from a database and displaying it on a list is a very common task in app development. One crucial task that’s often neglected when doing this, especially by new developers, is lazy loading the data to avoid fetching a whole collection, making your app slower and racking up your bill.
Paginating or lazy loading your data using Firestore is relatively simple — that is if you’re getting the documents once. When you attempt to lazy load your data using streams to keep your data updated in real time, it’s a whole other story.
In this article, I’m going to teach you a technique to lazy load your data from Firestore in real time using the Bloc pattern. If you don’t know anything about the Bloc pattern, I suggest you read their documentation to get an idea of how it works.
What We’re Building
We’ll build a simple app that lazy loads posts from a Firestore collection and displays them on a
Start by creating a new Flutter project:
$ flutter create lazy_loading_demo$ cd lazy_loading_demo
Add the following dependencies to your
Next, go to the Firebase console, and create a new project. Once the project is created, enable Firestore, and follow the instructions to add it to your app.
We’re going to need to create a repository that our Bloc can use to fetch data from Firestore. Create a new folder named
supplemental, and then create
post_repository.dart inside of it. Add the following code:
Now we have a simple repository we can use to get our data. The code is relatively easy to understand:
getPosts returns a stream containing the first 15 documents of the
getPostsPage returns a stream containing 15 documents from the collection, starting after the document we pass as an argument.
We created this repository as a singleton; however, you may want to use dependency injection or a lazy singleton in your app, depending on your use case.
We’re going to need a model to represent each post retrieved from the database. For this example, the following class will do:
Create a file named
models/post.dart and paste it into the previous code. Note that we’re extending
Equatable so we (and our Bloc) can compare instances of
Create a new directory called
data_bloc. There, create the following files:
Lets start by creating the events for our Bloc. In the file
data_events.dart, add the following lines:
Here, we’re creating a base class extending
Equatable so we can compare our events. Now that we have the class we’ll base our events on, let’s define which events our Bloc should have:
DataEventStart: Used to configure our Bloc
DataEventLoad: Used to notify the Bloc new data is available
DataEventFetchMore: Used to get more data once we run out
Add the following lines to
Now let’s define the states our Bloc will emit:
DataStateLoading: Used when the data isn’t loaded yet
DataStateEmpty: Used when there are no documents in the collection we pulled
DataStateLoadSuccess: Used when the data was pulled successfully and contains a list of posts
It may be a good idea to add a
DataLoadFailure state to emit in case something goes wrong. Try to implement it yourself.
Add the following code to
Here, we implemented the base class and the states we defined earlier.
Now it’s time to pull our Bloc together. Add the following to
Most of this code is very simple. When the Bloc receives
DataEventStart, it resets the class variables and cancels the stream subscriptions. It then creates the first subscription by requesting it from our
PostRepository and letting
handleStreamEvent (we’ll implement this method next) take care of the events received from the stream.
When our Bloc receives a
DataEventLoad event, it first flattens the list of posts and then checks if it’s empty and returns either
Our last event,
DataEventFetchMore, requests a new page of data from our
PostRepository and adds the stream subscription to our subscriptions list.
Now that we’ve finished most of our Bloc, let’s implement the
handleStreamEvent method. Copy the following into your Bloc:
handleStreamEvent method is simpler than it looks. It takes two arguments, whichever stream called it (passed as its
index in the
posts list), and the event received from the stream as a
When called, the method first checks the length of the
QuerySnapshot received. We request 15 docs at a time, so if we get less than that it means we’ve reached the end of the collection.
It then checks if it’s handling events from the last subscription in the list, and if that’s the case, it sets the
lastDoc variable to the last document in the query snapshot.
Finally, it updates the
posts list and adds
DataEventLoad to our Bloc.
Now that we have a mechanism to lazy load our data, it’s time to create a UI to test it. Create a new directory for your UI with a file named
lazy_list.dart. There, add the following:
The implementation of the UI is very straightforward. We return a
Scaffold with a
BlocBuilder, which handles all the states the Bloc can emit.
If the posts were loaded successfully, it returns a
ListView, which has an extra item if
true. In the list builder, if the current index is greater than or equal to the post-list length, it adds
DataEventFetchMore to the Bloc and displays a
CircularProgressIndicator in the meantime. Finally, we close the Bloc on the
One last thing, change your
main.dart to contain the code below:
Here, we just initialize Firebase and set
LazyListScreen as the home screen. We call
WidgetsFlutterBinding.ensureInitialized() so we can make a call to the Firebase Core plugin in our main function.
We’ve successfully implemented a lazy-loading list. This is a very simple implementation you can build on; in a real app, you may want to inject the Bloc to the UI using
BlocProvider or modify this example to suit your requirements. There are also different approaches you can take to implement this behavior — the one used here is just the simplest I could think of.