Getting Started with Firebase and Flutter

Cloud Firestore Basics in Flutter

How to Get, Add, Edit, and Delete Data in Cloud Firestore, Demonstrated in a Real Flutter App

Ash Jones
Firebase Developers

--

In this post, I will be demonstrating how to do the basic database operations in Cloud Firestore in a Flutter app, using a published app I built as a practical example.

You will learn how to implement the core database operations in Cloud Firestore: how to get (read), add (write), edit, and delete data in the database in a Flutter app.

Note: This article does not cover setting up Cloud Firestore, Firebase Authentication/Sign in for Flutter, and documents/collections in Cloud Firestore, which are prerequisites for this tutorial. If you are unfamiliar with these topics, I highly encourage you to read the Cloud Firestore plugin’s Readme and check out this code lab to setup Firebase with Flutter, check out the _Reply project as a guide, and watch this video to learn about documents and collections in Cloud Firestore before returning to this article.

_Reply App Background

_Reply is a Flutter app I built from scratch and published to the Google Play Store and Apple App Store. This is a rebuild of my previous native Android app “Reply”; I rebuilt it using Flutter to make it available on both iOS and Android.

I will use this app to provide clear, practical examples of how to implement the core Cloud Firestore operations.

I have the code available on Github and you can download the app on the Google Play Store and the Apple App Store.

_Reply helps you easily create your own custom, pre-defined messages through any platform.

With the app, you create your custom message templates which you can categorize based on the type of message (i.e. personal, social, and business messages). Each tab corresponds to a category.

Once you create your own messages, you can preview, send, edit, and delete them using the main button in the bottom right corner.

Registering and Signing in Users

You can also use Firebase Authentication to register and sign in users. If you are implementing Firebase Authentication for the first time, I recommend reading the _Reply source code, which will show you how to implement the following:

  • Sign in with email and password
  • Sign in with Google
  • Sign in with Apple
  • Sign out
  • Register with email and password

All of these methods can be found in the project’s AuthService.dart class.

❗️❗️Please note that authenticating/signing/registering a user does NOT create a user for you in the database.

Signing in a user and creating a user in the database are two separate operations you must implement.

This can be confusing because once a user has signed in, you will see an authenticated user with a uid within the Authentication tab in the Firebase console. These are the Firebase users. After a successful initial sign in or registration, a FirebaseUser is created and signed into the app. A FirebaseUser contains data such as name (called ‘display name’), email, and an automatically generated uid to identify the FirebaseUser.

Before we start adding user data to the database, we first need to create users which we will save the user data to in the database. This should be done after registering/signing in to the app and only if it is a new user.

To create users in our actual database, we will use the data provided by the FirebaseUsers to create users in our database; namely, we will use the uid and the name.

Checking For New Users

After a successful sign in, we should have a FirebaseUser signed in.

We do not need to check if it is a new user when signing in with email, because if the account does not exist, we will show a message to first register the account:

To check if it is a new user when signing in, we will compare the creation time stamp and last sign in time stamp of the FirebaseUser. This is what we will do for the sign in with Google and sign in with Apple options. We can do this like so:

if (firebaseUser.metadata.creationTime
.difference(firebaseUser
.metadata.lastSignInTime)
.abs() <
Duration(seconds: 1))

Let’s take a look at how this is done when signing in with Google.

When the sign in with Google button is pressed, we will execute the following. Notice how after the user has been authenticated (signed in) with Google, we compare the creation time and last sign in time to check if it is a new user. If a user is signing in for the first time, we create a new user in the database, passing the FirebaseUser to the method so we can utilize the data it contains.

Note: I ran into a quirk when comparing the creation time and last sign in time. For some reason during the initial sign in, the time stamps were off by a thousandth of a second, when they should have been the same. Thus, I could not compare them using equality, because they were always off. To address this, I compared the difference between the two, checking to see if there is at least a one second difference.

Creating a New User in the Database

To create a new user in the database, we will do the following:

  • Use the authenticated FirebaseUser to get the user’s name and email.
  • Create a collection called “users” and add a user (a document) to it, setting the document id as the FirebaseUser uID, the name field as the FirebaseUser’s display name, and the email field as the FirebaseUser’s email.

If done successfully, we should see a users collection with a user document:

Each document in Cloud Firestore has a document ID to identify it. We use the setData method instead of the add method so we can explicitly set a document ID instead of it being automatically generated. We do this to easily retrieve the document later. However, please be aware this could cause potential conflicts.

When a user is signed to our app, we can call:

FirebaseUser firebaseUser = FirebaseAuth.instance.currentUser()

Since we set the document ID (for the document of the FirebaseUser) to be the same as the corresponding FirebaseUser uID , we can simply retrieve the document using the FirebaseUser’s uid:

firestoreInstance.collection(USERS_COLLECTION).document(firebaseUser.uid).updateData({
// Update document
)}

Now let’s get to the meat of this article and learn how to work with data and the database.

Adding Data to the Database

Setting Up Security Rules

Setting up Cloud Firestore Security Rules is important to keep your users’ data secure. Furthermore, if you don’t change the initial security rules, your app‘s Cloud Firestore database access will expire after 30 days. I recommend reading how to structure security rules to apply the appropriate restrictions to your app. The following set of rules ensure that only authenticated users can read and write their own data:

Adding Initial Data

Before we get into the add feature of _Reply, let’s learn how to add data when we first create users in the database. This is useful to have placeholder data, such as welcome messages when the user first signs in:

Placeholder message in _Reply

In this case, we want to add placeholder messages when the user is first created so we can show welcome messages.

We are going to use the MessageCard model class to add messages. Notice the toJson() and fromJson() methods. To add custom objects to Cloud Firestore in Flutter, we must first serialize the data. In this case, we are serializing the inside the MessageCard model class, converting our objects into a map that Cloud Firestore will accept.

If you would like to read more about serialization in Flutter, check out this post.

Now we can simply add fields when we are saving the user to the database. Here we are adding 5 new fields, one for each category of messages, and adding MessageCards (messages) that display the user’s name, using the toJson() method to serialize the data.

Adding Data to an Existing Document

Add Message in _Reply

Adding data to Cloud Firestore can be implemented in multiple different ways.

The add, setData, and updateData, and updateData + FieldArray.union methods can all be used to add data.

I recommend reading the documentation to fully understand when to use each add method, but here is a brief overview:

  • add(): Add a document with an auto-generated id
  • setData(): Add or overwrite a document with an explicit id. If the document does not exist, it will be created. If the document does exist, its contents will be overwritten with the newly provided data
  • updateData(): Add fields to document without overwriting the entire document
  • updateData() + FieldArray.union():Add an element to an array field of a document.

Adding New Data to an Existing Array Field

Now we are going to learn how to add data. This is how data is added in _Reply when a user adds a new message.

Since we created placeholder data, we already have a fields for the messages, which are lists. In Cloud Firestore, lists are stored as arrays, so we are actually working with an array field.

In this case, we have to update an existing array field; we must add an element (message) to the list while retaining the existing elements (messages). To do this, we use updateData + FieldArray.arrayUnion.

Notice we must first serialize the data using the toJson() method to save the data to the database. To store it as a list of items instead of a map, we convert the map to a list.

If executed correctly, we should see the new message added to the existing array field. (In this case, the personalMessages field)

Deleting Data From the Database

Deleting data from Cloud Firestore can be implemented in multiple different ways.

Again, I recommend reading the documentation to get a full grasp of when to use each method. Here is a brief overview:

  • delete(): Delete data from a document
  • updateData() + FieldValue.delete(): Delete a field from a document
  • updateData() + FieldArray.remove(): Remove an element from an array field within a document

Similar to the add message case, we need to update an existing array field; we must remove an element (message) from the list while retaining the existing elements (messages). To do this, we use updateData() + FieldArray.remove().

If implemented correctly, the message should be deleted and the field, personalMessages, should still contain the other messages. Here we removed the the “Get Together” message, which happened to be 1st element in the array field.

Editing Data in the Database

Continuing with our trend, editing data also has multiple implementations and use cases. Let’s take a look:

  • updateData(): Update fields of a document without overwriting the entire document
  • setData() with merge:trueUpdate fields in a document or create it if it do not exist
  • updateData() + FieldValue.arrayRemove() + updateData() + FieldValue.arrayUnion():Update elements in an array field within a document
  • updateData() + FieldValue.increment():Increment a numeric field within a document

For the _Reply app, what we need to do is replace the old message with the new message. To do this, we will use updateData() + FieldValue.arrayRemove() + updateData() + FieldValue.arrayUnion():

Notice what is going on here. To update the message, we are actually just deleting the old value, then adding a new one- a clever way to edit something.

Getting Data From the Database

Now that we have data in the database, we can retrieve it. I saved getting data for last because it is the most cumbersome and complex data operation to implement.

Fortunately, there is really only one method we need to know when getting data: get().

Here is what we need to do to retrieve our data:

  • We first create an empty List to hold the data we will get from our database
  • We then query the database for the data we need
  • Since the data is returned to us as a Map, we need to convert it to MessageCard objects so we can easily retrieve the data; to do this, we use the fromJson() method in our MessageCard class
  • Once our data is converted to MessageCard objects, we can get the data we need; in this case, we are retrieving the MessageCards and adding them to a list.
  • Finally, we return the list of messages to show them in the UI

Since the data returned from Cloud Firestore will be a Map, we need to convert it to deserialize it and convert it to a custom object. This is the opposite of what we had to do when saving the MessageCard objects to Cloud Firestore. We use the fromJson() method to do this.

Conclusion

Let’s recap. We learned how to:

  • Check for new users upon sign in
  • Create new users in a Cloud Firestore Database
  • Set basic security rules
  • Add placeholder data for our users
  • Add new data to the database
  • Delete data in the database
  • Edit data in the database
  • Get data from the database

I know that was a lot, but you made it!

Now you can truly be a (Fire)base User. 🔥

Resources

--

--

Ash Jones
Firebase Developers

Philosopher. Writer. Writing about self-development, focus, eliminating distractions