Exploring data operations using cloud_firestore in Flutter
In this article, we will take a look at some basic concepts used in FlutterFire’s cloud_firestore
plugin and how to make use of various methods offered by it for data operations such as adding and retrieving, so let’s get on with it straight-away.
Agenda
Below are the areas of the plugin I will cover today:
- What is a Collection, how to create one and refer it while querying the data?
- What is a Document, how to create one and refer it while querying the data?
- What is a QuerySnapshot, DocumentSnapshot and AsyncSnapshot ?
- How to retrieve data from a collection based on different conditions?
Note: This article will not cover how to create a new Flutter app and a Firebase project and how to integrate these two. I will assume that there’s already a Flutter app integrated with Firebase with all required settings and configuration done. For more details and reference, please follow this documentation to create a new Flutter app, integrate Firebase project with firestore database created, add required plugins in project such as
firebase_core
andcloud_firestore
so that the app is in running state.
Starting point
Once we have the app running, first step is to initialise the FlutterFire using it’s CLI tool to integrate your Firebase project with your Flutter app using flutterfire configure
command, which in turn, writes the native configuration files to the root of your Flutter project.
flutterfire configure
generates firebase_options.dart
file and puts it inside lib
folder of your project and then we simply need to refer it by calling initializeApp
method on Firebase
class as below in our main
method:
You can read more about this CLI tool here.
Data to work with
We will create data in the form of documents
programmatically using the methods offered by the plugin. So, we will work on to create following data:
In order to add above data in firestore db, we will first need to know some basic terms that we are going to use / see today. They are:
- Collection: A
collection
is a top-most level node in hierarchy of a firestore database. Think of it as a starting point for a data stored and has a name, in our case, we will name itcity_data
. In other words, considercollection
to be a name of a table from a traditional database point of view.
Since firestore is a NoSql database, it stores the data in the form of key-value
pair. The actual data, is stored in the form of a document
which forms the next level of node in hierarchy, below collection
. Each document
will have an entry of holding data of a different city.
With above details, we will start to create a collection now. In order to do that, we will use CollectionReference
class and create an object of it to refer throughout our data creation and other operations.
After creating a collection object, we will now see what are the different methods offered by it and we will print them to see what they represent.
There are several other methods offered by it, but it will make sense to go over them after we add some data to it.
Let’s now create first document with city data by declaring a var
and then adding required key
and value
pair like below:
We will then pass cityOne
to add()
method like below to add our first document in the db:
After running our Flutter app, in the firestore database created in Firebase console, it creates the collection city_data
and adds the first document with data as above. Just refresh the browser to see the newly created collection and respective document inside it as below:
Likewise, we will create 4 more documents, so that we will have a handful of data to work with:
Now, in order to read all above documents, we will make use of get()
method as below and we will print all documents with respective document fields:
Likewise, we can also reference a particular document field by using DocumentReference
class, create an object of it and see what methods are offered for it which we can use.
Let’s use some of the methods listed above, print them and see what they return:
We will cover some of the methods below to make more sense of their usage.
Filtering data based on conditions
In production applications, users sometimes want data specific to some conditions, so in this case, we will use filtering on the data created and print that data. Some of the filtering methods provided are orderBy()
, where()
, limit()
and equality operators that we will see below.
orderBy()
This method is used to display the data in a specified order, either descending
or ascending
. The method orderBy
takes required argument of field
followed by optional descending
taking a bool
. Accordingly, we will query the database to print the result in descending order of city
, as below:
Observe that city
is sorted descending.
Note that we used QuerySnapshot
class which is a snapshot
returned as a query result. The snapshot
contains DocumentSnapshot
objects. It exposes docs
method which returns all documents from the snapshot that are present in DocumentSnapshot
.
A DocumentSnapshot
is a snapshot that returns data of a single document
. It exposes data
property through which the data is extracted.
We can combine limit()
method with orderBy()
to restrict the number of documents to retrieve / display, like below:
where()
This method is used to query and retrieve data based on specific condition. From our data, we will retrieve a document whose capital:true
, as below:
The Equality operators are an optional field we can provide in where()
method which helps to further narrow down our query. Below are the equality operators offered:
We will see another example with isGreaterThan
and will retrieve the data based on population
, as below:
Similarly, we can make use of isLessThan
, isEqualTo
and other equality operators listed.
We can also combine orderBy
with where
clause to retrieve data based on certain condition. For example, show data whose capital is not true
, as below:
Important thing to note in above query is that, whenever we are using
orderBy
andwhere
together, we need to query it on same field, ie,capital
in above example, otherwise, if we pass different fields in both clauses, we will get below error:
Reading the error message carefully, it states, The initial orderBy() field ‘[[FieldPath([city]), false]][0][0]’ has to be the same as the where() field parameter ‘FieldPath([capital])’ when an inequality operator is invoked.
Indicating that we can make use of orderBy()
field again after initial query and pass different field to it, as below:
In order for above query to work properly, make sure you have already created a composite index in Firebase console as below, else, it will throw an error indicating to create one. The error message has a link to create the required index so just follow along the link.
Let’s see another example by using whereIn
method along with where
clause, which returns all documents that matches the fields we provide inside whereIn
. Consider it as a OR logical operation.
The query using whereIn
returns data which matches either condition we pass in whereIn
. For example, if there’s no data matching either of condition, it will return the data that matches the condition and will not throw an error, as below:
In above example, there’s no data matching country as Canada
, but the query returned the one matching with Aus
. Similarly we can try a query with whereNotIn
.
Similarly, there are arrayContains
and arrayContainsAny
conditions inside where
clause which essentially works on array
data type, ie, these conditions will work only on array
datatype. An example using arrayContainsAny
is below:
While arrayContainsAny
expects List<Object>
and works as above, the same query will not work for arrayContains
which expects Object
directly, so, in order to use arrayContains
, we will modify above query that will properly return results, as below:
Displaying data in UI
In above examples, we printed the query results in console and now we will see how to make use of Flutter’s widgets to display retrieving data in UI. Let’s do that and display using ListTile
as below:
Wherein, _cityStream
is declared as a QuerySnapshot
Stream as below:
final Stream<QuerySnapshot> _cityStream = FirebaseFirestore.instance.collection('city_data').snapshots();
In above code snippet, we used AsyncSnapshot
which is used for all async operations. It exposes ConnectionState
like none
, waiting
and done
to properly handle the conditions at various stages while retrieving the data. The class also helps us to handle conditions when there is an error or when the data source is empty and based on such conditions, we can decide what to do.
Running the code, the UI will show data as below:
With this, we covered what are the various methods offered by cloud_firestore
plugin and how we can make use of them to retrieve data stored as part of collection
and document
.
That’s all I have for today. Thanks for reading and feel free to comment below your thoughts / suggestions / feedback on this topic.
I can be reached via Twitter and LinkedIn.
You may check out my profile below for other articles I wrote on Flutter.
Follow Flutter Community on Twitter: https://www.twitter.com/FlutterComm