Loading data from Firestore with Flutter

In a recent side-project of mine I needed to perform some simple loading of data from an external data source. This was all data that I had collated myself so I decided to manually load it up into Firestore — in this post we’re going to quickly take a look at how we can hook up our Flutter application to load data from a Firestore datastore to display to our users. This post will hopefully act as both a guide to show you how to implement Firestore in your apps and also a way to show you how little work is involved to do so.



To be able to load data from Firestore you’ll first need a datastore — this is essentially a remote database. Data in this databases can be used for multiple purposes — maybe it is read-only data, or maybe you want users to be able to manipulate data within the database. The datastores are pretty flexible in what you can do with them, but for now we’re going to concentrate on just querying data from the datastore and loading it up into our application.

So now that we have a datastore, we need to start by adding the Firestore package to our pubspec.yml file, we can do this like so:

dependencies:
  cloud_firestore: ^0.8.2+1

We now need make use of this package within our application. For these, we’re going to add the following to the build function of our widget:

@override
Widget build(BuildContext context) {
  return StreamBuilder<QuerySnapshot>(
    stream: Firestore.instance.collection(name).snapshots(),
    builder: (BuildContext context, 
              AsyncSnapshot<QuerySnapshot> snapshot) {
      return new ListView(children: createChildren(snapshot));
    },
  );
}

Let’s break this code down a little bit:

  • First we define a new StreamBuilder with the type QuerySnapshot. A stream builder is a widget that will take a stream of data and essentially observe it for changes — that way when the stream updates, the UI will update.
  • For the stream argument of this instance, we begin by fetching an instance of Firestore by using the Firestore.instance reference.
  • Using this Firestore reference we then fetch our desired collection using collection(name). Using the collection name from our datastore, this gives us a CollectionReference which corresponds to the collection in our data store.
  • Then using the snapshots() function from this collection reference, this gives us a stream for the data from this reference.
  • Next, for the builder argument we define the visual widget that will be built using the data in the given stream. Here we essentially return a list that contains widgets made up from this data. I don’t want to focus on how we can build these widgets here, but for examples sake we could built a text widget using the data stream like so:
return snapshot.data.documents
    .map((document) => new Text(document['some_field']).toList()

Here we essentially take the items of our stream, create a text widget for each one and then return a list of these widgets. The part I want to focus on here is the accessing of document data. To access fields from a document you can do so as shown above with document[‘some_field’]. Here we use our document reference from our map operation and then access a piece of data from that document using the field name as an accessor.


There will also be times when our user is waiting for the data to come back from our stream — in these cases we want to keep the user in the loop that things are currently loading.

For this we can make use of the connectionState property that our AsyncSnapshot holds. The key state here for this is the waiting connection state, in the below code you can see how we can use this to show a loading indicator instead of the content list:

@override
Widget build(BuildContext context) {
  return StreamBuilder<QuerySnapshot>(
    stream: Firestore.instance.collection(documentName).snapshots(),
    builder: (BuildContext context, 
              AsyncSnapshot<QuerySnapshot> snapshot) {
      switch (snapshot.connectionState) {
        case ConnectionState.waiting:
          return new Center(child: new CircularProgressIndicator());
        default:
          return new ListView(children: getExpenseItems(snapshot));
      }
    },
  );
}

But as well as loading states, we will also have times where things don’t quite go as planned — and inn these situations, we want to let the user know that an error has occurred. For this, we can access the hasError property from our snapshot instance — as shown below, this will allow us to show an error state as our UI content:

@override
Widget build(BuildContext context) {
  return StreamBuilder<QuerySnapshot>(
    stream: Firestore.instance.collection(documentName).snapshots(),
    builder: (BuildContext context, 
              AsyncSnapshot<QuerySnapshot> snapshot) {
      if (snapshot.hasError) return new Text('${snapshot.error}');
      switch (snapshot.connectionState) {
        case ConnectionState.waiting:
          return new Center(child: new PlatformProgress());
        default:
          return new ListView(children: getExpenseItems(snapshot));
      }
    },
  );
}

As a final point when it comes to retrieving and handling data, we also have control over the data that we wish to retrieve from our collection. Originally we created our stream like so:

Firestore.instance.collection(documentName).snapshots()

We can also break this down a bit to query for specific data from our collection reference — for example, here we could use the where function to retrieve data only where a specific condition is satisfied. So say if we had a countryfield we could perform a where operation where the country property matches a given country. As you can see below, this where function takes a string value for the field which we wish to query again, follow by the property which we wish to query this against.

CollectionReference collectionReference =
    Firestore.instance.collection(documentName);
return StreamBuilder<QuerySnapshot>(
    stream: collectionReference.where("country", isEqualTo:
        country).snapshots(),
    ...
)

As well as performing a where query to check for items that match isEqualTo, we can also query where isLessThanisLessThanOrEqualToisGreaterThanisGreaterThanOrEqualTo and arrayContains to query for specific conditional data.


I hope this article has given you some insight into how you can get started with storing and reading data from firestore in your Flutter applications. If you have any thoughts, questions or just want to share how you’re using firestore in your apps then please do get in touch 🙂

Written by
Go to the profile of Joe Birch

Joe Birch

Android Engineering Lead at Buffer, Google Developer Expert for Android & Flutter – Passionate about mobile development and learning. www.joebirch.co

FlutterDoc

FlutterDoc

Snippet sized documentation for the Flutter Framework

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *