Storing data to the cloud using Firestore - Todo app #2

Second part of the Todo blog series, exploring Cloud Firestore database integration with FlutterFlow

5 months ago   •   21 min read

By Souvik Biswas
Table of contents

Cloud Firestore is a flexible and scalable NoSQL database to store and sync data across multiple devices in real-time. Another advantage of using this database is its built-in offline support for mobile and web apps. When your mobile device is offline, it stores the data generated by your app locally, and once the device comes back online, the data is synced with the cloud database.

In this article, you will learn to set up and integrate the Firestore database with your FlutterFlow app. Once the Firestore integration is complete, we'll progress with our ToDo app by adding functionality for storing, retrieving, updating, and deleting tasks.

Clone the starter project for this tutorial to follow along: Todo Starter 2.

This is the second part of the ToDo blog series, and if you haven't yet read the first part, we recommended you go through it before continuing with this blog:

Integrating Firebase Authentication and Google Sign In - To Do App #1
The first part of the ToDo blog series, a walkthrough of integrating Email sign-in and Google Sign-In using Firebase Authentication to a FlutterFlow app

What's a NoSQL database?

Before going further into the Firestore database, let's take a step back and look at the broader picture of databases.

The two most important types of databases are:

  • SQL: A relational database system having data items with pre-defined relationships between them that are organized as a set of tables with columns and rows.
  • NoSQL: A non-relational database system having flexible data models, scalable horizontally, helps in fast queries and is easy for developers to work with.

In Cloud Firestore, data is stored inside documents in key-value pair format called fields, which are organized into collections.

The following video gives an excellent visual explanation of the differences between a SQL and a NoSQL database, along with a basic idea of the Firestore database structure:

For anyone just starting with Firestore, it's essential to understand the structure followed by this NoSQL database. The following section covers the data model of Firestore in detail.

Firestore data model

You might have already figured out that Firestore uses collections, documents, and fields for structuring its database.

A Collection can contain one or more documents. For example, a users collection can contain various users, each represented by a document.

A collection called "users" containing three documents: user1, user2, and user3

Document can contain fields and sub-collections. For example, a user1 document can contain various user details as fields. Let's say you also want to store hobbies for each user along with the date it's added, then the best way is to create a sub-collection called hobbies and store each of the hobbies as separate documents.

A document called "user1" containing two fields: name and country, and another document called "user2" containing two fields: name and country along with a sub-collection called "hobbies"

Field is basically a key-value pair where you can set the key as the field name, and the value can have a data type such as string, number, boolean, etc.

Learn more about the supported data types in Cloud Firestore from here.
Three fields (name, country, and age) along with their values, showing the key-value pair structure

Setting up Firestore

With the basics out of the way, you are ready to start setting up Firestore.

Follow the steps below to set up Firestore:

1. Navigate to the Firebase Console.

2. Select Firestore Database from the left menu and click Create database.

Firestore Database page opened from Firebase console

3. In the dialog, choose Start in test mode option and click Next.

⚠️
This is a fully open mode; as you can see, the warning message tells that anyone can view, edit and delete all data from the database. It's not recommended to use this mode and should be updated before deploying the app to production.
Create database dialog (step 1), choose the type of security rule
NOTE: There's a dedicated section on security rules later in this article, and at that time, we'll update the security rules. That being said, you won't need to go into too much detail of security rules as FlutterFlow handles most of them for you using a simple interface.

4. Select a location from the dropdown options and click Enable.

Create database dialog (step 2), set Cloud Firestore location
Check out this link for more information about Firebase locations.

Creating Collections

You can directly create and structure your Firestore database from FlutterFlow. For the ToDo app, you would need two collections:

  • users: For storing general user information.
  • todos: For storing the todos of each user.

Users Collection

Having a users collection is a basic necessity for most apps, so there's a simple way to auto-generate some of the fields you may require.

If you want to follow along, clone the starter project:

  1. Login to FlutterFlow here. If you don't have an account, you can create one easily from the same page.
  2. Open the starter project for this tutorial and clone the project.
⚠️
The starter project requires you to connect your Firebase project. If you are not sure how to do it, follow the steps here (just skip the cloning steps there).

Follow the steps below to generate the users collection:

1. Navigate to your FlutterFlow project, select Firestore from the left menu, and click + Create Collection.

Firestore page inside FlutterFlow with the Create Collection button

2. In the dialog box, enter the name of the collection as users. Click Create.

Create Collection dialog with "users" as the collection name

3. Go to the Settings tab, and enable the Users Collection using the toggle. From the Collection dropdown, choose users.

Firestore settings page inside FlutterFlow, enabling the users collection using a toggle

4. A dialog box will pop up for whether you want to add the necessary fields to the users collection, click Yes.

Dialog box for wherther the necessary fields for the users collection should be generated automatically

5. If you go back to the Collections tab, you will notice that a few fields have been added to the users collection.

Users collection wih the generated fields

6. To create a new field, click the "+ Add Field" button.

Add field button on users collection

7. Enter the field name, data type, and field type (single element or list). Click Create.

Dialog box for adding a field to the users collection

8. You can remove any fields you don't need by clicking on the remove icon beside the field. Click Delete in the dialog box.

Delete button beside a collection field

Todos Collection

Let's add the todos collection that would be used for storing data related to each of the to-do items that the users can add, edit, or remove.

This collection will consist of the following fields (the data types are written in braces):

  • title (String): store the name of to-do task
  • description (String): description of to-do task
  • timestamp (Timestamp): creation date of the to-do task
  • is_done (Boolean): whether the to-do task is complete
  • uid (String): unique identifier of a user

Follow the steps below to create the todos collection:

1. Click on the "+" button present beside the Collections.

Add button beside the Collections text inside FlutterFlow

2. Enter the collection name and click Create.

Create collection dialog with todos as the collection name

3. Once the collection is created, you can add fields to it by clicking "+ Add Field".

Add Field button inside the todos collection

4. Enter the field name, data type, and field type. Click Create.

Dialog box for adding a field to the todos collection

5. Follow similar steps for adding the rest of the fields.

Once all the fields are added to the todos collection, it would look like this:

todos collection with all the required fields added

Adding security rules

Now that you have the basic structure of the two collections ready, it's time to look at the Firebase Security Rules.

⚠️
Disclaimer: Don't skip this section. You should ALWAYS configure the security rules before starting to use the database.

The security rules help to keep your Firebase data secure from malicious users. Firebase allows you to set security rules on Cloud Firestore, Firebase Realtime Database, and Cloud Storage.

Today we'll only go into the details of the security rules of Cloud Firestore.

FlutterFlow makes it a lot easier to manage your Firestore security rules. You can access it by going to the Firestore page > Settings tab > Firestore Rules.

Every operation (or database request) done on the Cloud Firestore is evaluated against your security rules before reading or writing any data. You can configure the security rules for the following four types of operations:

  • Create: users having access can create a new document inside a collection
  • Read: users having access can read documents of a collection
  • Write: users having access can update a document of a collection
  • Delete: users having access can delete a document of a collection
CRUD (create, read, update and delete) is the acronym used to refer to these four basic database storage operations.

In FlutterFlow, you can select among the following five levels of access for your data:

  • Everyone: Allows both authenticated/unauthenticated users to perform the operation
  • No One: Allows none of the users to perform the operation
  • Authenticated Users: Allows only the authenticated users to perform the operation
  • Tagged Users: Allows users to perform the operation if they have been tagged in the document. To use this rule, you need to select a field from the document representing that user.
  • Users Collection: (can only be set on the operations of Users Collection) Allows users having uid (unique user identifier) same as the id of the document to perform the operation.

The default rules that FlutterFlow sets on any new collection are as follows:

Operation Access Description
Create Everyone Any user can create a document
Read Everyone Any user can read a document
Write No One None of the users can write to a document
Delete No One None of the users can delete a document

As there are predefined rules for the users collection, you can just set the access level of all the four types of operations as Users Collection.

Let's take a look at what should be the ideal security rules for the todos collection.

  • Any authenticated users should be able to create a to-do task.
  • Only tagged users (having the same uid with which they are authenticated) should be able to read, update (write), or delete a to-do task.
These rules will allow any authenticated user to create a to-do task, but only the user who has created that task would be able to read, update or delete it. So this will prevent any task created by a user from being read or modified by a different user.

According to the above points, you can set the following security rules to the todos collection:

  • Create   –> Authenticated Users
  • Read     –> Tagged Users (with uid field)
  • Write     –> Tagged Users (with uid field)
  • Delete   –> Tagged Users (with uid field)

On selecting the Tagged Users rule, a dialog will appear where you have to choose the field (whose value will be used for verifying the tagged user). Choose uid as the field and click Save Changes.

Once you have the security rules defined, you can also see a preview of the rules. Now, click on the Deploy button to upload the rules to Firestore.

This will open up a dialog showing the updated security rules that you are going to upload. Click Deploy Now.

Notice that the following part is still present in the security rules:

match /{document=**} {
	allow read, write: if
		request.time < timestamp.date(2022, 5, 25);
}

This was added earlier as we were using the database in test mode, but now you won't need this part. Remove this rule by following the steps below:

  1. Go to your Firebase project.
  2. Select Firestore Database from the left menu.
  3. Go to the Rules tab.
  4. Remove that part from the rules, and click Publish.

UI Overview

We'll continue using the interface of the RobinDo app, which you can find among the sample apps of FlutterFlow.

In the previous article, we haven't completed the interface of the MyTasksScreen, so we'll start with that.

Our focus would be mainly on implementing the CRUD database operations in the app, so we won't go deep into creating the UI.

MyTasksScreen

Previously, we had created this page to just contain the Log Out button in order to demonstrate the logging out action of authentication.

Update this page to display the list of all tasks added by the user (reading from the database):

CompletedTasksScreen

Displays a list of all the tasks that the user has already completed (reading from the database).

CreateTaskScreen

Users can create or add a new task from this page by entering the required details.

TaskDetailsScreen

Displays the details about a task when a task item is selected from the list. This page will have an option to update or remove the task from the to-do list.

EditTaskComponent

This component will be displayed as a bottom sheet when the edit button is tapped on the TaskDetailsScreen. You can edit the details of a to-do task using this component.

MyProfileScreen

The Log Out button can be shifted to this page. It will also have a button for going to the ProfileEditScreen.

ProfileEditScreen

Users can modify their current profile details from this page.

Adding NavBar

Let's add a bottom navigation bar (or, NavBar) to facilitate quick navigation between the top-level views of the app. We'll add the NavBar to the following pages:

  • MyTasksScreen
  • CompletedTasksScreen
  • MyProfileScreen

Follow the steps below to add a NavBar:

  1. Go to a page on which you want to add the NavBar.
  2. Enable the Show on Nav Bar toggle.
  3. Enter a Label text using which you can easily identify the item.
  4. Choose a Nav Bar Icon.
  5. Enter the Icon Size to be 32.
  6. Repeat the above steps for adding the NavBar on other pages as well. You should have the NavBar enabled on at least two pages.
  7. Navigate to the Settings and Integrations page from the left menu.
  8. Select NavBar & AppBar under General.
  9. Enable Show NavBar toggle.
  10. You can set a Nav Bar Style and customize various properties of it from this page.

Performing CRUD operations

There are three types of Database Actions that can be used to create, update, or delete a record from a Firestore Collection.

Data can be read from the database by defining a Backend Query. You can either retrieve all the documents present inside a collection or just retrieve the data present inside a single document.

This section will give you a walkthrough of performing the CRUD operations on the users and todos collection.

Creating user document

In the last article of this series, we discussed how to define a user authentication flow, but we didn't store any of the user details inside the Firestore database. While building most apps, you need to store the general user details in order to create a good profile for them.

Using Email sign up

To store user details while registering using email/password, you must manually specify the values for the fields you want to store in the users collection.

Follow the steps below:

  1. Navigate to the RegisterScreen page.
  2. Select the Create Account button.
  3. Go to the Actions tab.
  4. Under the Auth Create Account action, enable Create User Document using the toggle.
  5. Select the users collection from the dropdown under Collection.
  6. Set the field values for email, display_name, and created_time.
    Use the Value Source as From Variable, select the Source as Widget State, and choose the respective TextField from the Available Options dropdown.

Using Google provider

While using Auth Login Action with the Google provider, just enable the Create User Document toggle. It will automatically assign field values of the users collection (you don't need to specify them manually).

  1. Navigate to the SplashScreen page.
  2. Select the Sign in with Google button.
  3. Go to the Actions tab.
  4. Enable the Create User Document toggle.
  5. Select the users collection from the dropdown.
  6. On the dialog box, click Yes.

Reading user details

Backend Query can be used for retrieving the details from the users collection. We'll need to add the backend query on the MyProfileScreen page.

  1. Navigate to the MyProfileScreen page.
  2. We want the query results to be available throughout the page, so you don't need to select any particular widget. Just go to the Backend Query tab from the Properties Panel.
  3. Select the Query Type as Query Collection.
  4. Select the users collection from the Collection dropdown.
  5. In the next Query Type dropdown, choose Single Document.
  6. Disable the Hide Widget If No Match because we don't want an empty widget if the user's display name is not found (also, as it's part of the registration, so it should always be available).
  7. Click Save.
Backend queries get triggered as soon as a page/widget loads on which it is defined. You can set only one backend query on a particular widget.

Once this query is defined, you should have access to the users collection data throughout the page.

To display the name of the user from the query result, follow the steps below:

  1. Select the user name Text widget.
  2. Click Set from Variable on the Properties Panel.
  3. Choose the Source as users Record.
  4. Under Available Options, select display_name.
  5. Click Save.

You would also want to pass the record data over to the ProfileEditScreen as you need access to the record for performing an Update Record action.

TIP: It's always recommended to pass a record data instead of having the same Firebase query again on the page you are navigating to. This helps in reducing the number of database queries per user.

Follow the steps below to pass the record to ProfileEditScreen from MyProfileScreen:

  1. Navigate to the ProfileEditScreen.
  2. Click on the edit button beside Parameters and click + Add Parameter.
  3. Enter the Parameter Name as profileData.
  4. Set the Type as RecordReference and choose users from the Record Type dropdown.
  5. Click Save.
  6. Navigate back to the MyProfileScreen.
  7. Go to the Navigate action defined on the Edit Profile Row.
  8. Click Pass under Parameters.
  9. Choose profileData as the Parameter from the dropdown.
  10. Select the Source as users Record.
  11. Under Available Options, choose reference.

Editing user details

We have already created the interface of the ProfileEditScreen and passed the users record reference to this page. Let's add the functionality of letting users edit (or update) their details present on the database.

Follow the steps below:

  1. Select the Save Changes button.
  2. Go to the Actions tab from the Properties Panel.
  3. Click + Add Action.
  4. Make sure On Tap is selected under the Type of Action.
  5. Select Update Record under Database.
  6. Choose the Source of the reference as profileData.
  7. Click + Add Field and set the values for email and display_name fields.
    Select the Value Source as From Variable, select Source as Widget State, and choose the respective TextField under Available Options.

Adding a todo task

For creating a new task record on the database, you have to use the Create Record action. Before adding this action, you need to define a Date/Time Picker action on the CreateTaskScreen to let the users select a task completion deadline.

To add the Date/Time Picker action, follow the steps below:

  1. Select the widget on which to set the action.
  2. Go to the Actions tab from the Properties Panel.
  3. Click + Add Action.
  4. Make sure On Tap is selected under the Type of Action.
  5. Select the Date/Time Picker action.
  6. Choose the Picker Type as Date.
  7. Select the Default Date from a variable, choose the Source as Global Properties and select Current Timestamp under Available Options.
  8. To display the selected date, select the Text widget.
  9. From the Properties Panel, click Set from Variable.
  10. Choose the Source as Widget State, under Available Options choose Date Picked, and select the Date Format Option as MMMMEEEEd.
  11. In the Default Value field, enter "Select Date" (which will be displayed when the user does not yet pick the date).
  12. Click Save.

For creating a new task record on the database, follow the steps below:

  1. Navigate to the CreateTaskScreen.
  2. Select the Create Task button.
  3. Go to the Actions tab from the Properties Panel.
  4. Click + Add Action.
  5. Make sure On Tap is selected under the Type of Action.
  6. Select Create Record under Database.
  7. Choose the action as Create Record under Database.
  8. Select the Collection as todos.
  9. Click + Add Field to add new fields to this record.
  10. Choose the value of the title and description from variable with the Source as Widget State, and under Available Options choose the respective TextFields.
  11. Choose the value of the timestamp field from variable with Source as Widget State, and under Available Options choose Date Picked.
  12. Choose the value of the is_done field from a specific value with the Value as False.
  13. Choose the value of the uid field from variable with Source as Authenticated User, and under Available Options choose User ID.

Add another action after the Create Record action to Navigate the back from this page:

  1. Select the Create Task button.
  2. Go to the Actions tab from the Properties Panel.
  3. Open using the Action Flow Editor.
  4. Click on the "+" button and select Add Action.
  5. Choose the action as Navigate Back.
  6. Click Close.

Displaying todo task list

You can quickly generate a list of widgets from the documents returned from a collection query. We'll also look at how you can apply filtering and ordering to the results.

Follow the steps below:

  1. Navigate to the MyTasksScreen.
  2. Select the Column widget containing the task tile widgets.
  3. Go to the Backend Query tab from the Properties Panel.
  4. Select the Query Type as Query Collection.
  5. Choose the Collection as todos.
  6. Select the Query Type as List of Documents.
  7. We only want to show the tasks created by the currently logged-in user. You can filter the results according to the uid. Click + Filter button.
  8. Choose the Field Name as uid.
  9. Select the Relation as Equal To.
  10. Choose the Value Source as From Variable, select Source as Authenticated User, and under Available Options choose User ID.
  11. We also want to apply an ordering on the results. Click + Order By button.
  12. Select the Field Name as timestamp and choose the Order as Increasing.
  13. Click Save.
  14. On the dialog box that appears, click Ok. This will generate the children dynamically inside the Column.

After defining this query, you might see an issue pop up saying, "Firebase indexes not properly deployed for your project".

Just click on that issue; it will take you to the Firestore Settings page. Scroll down to the Firestore Indexes section and click the Deploy button. You need to deploy the indexes while using ordering and filtering on the Firestore results.

To display the data retrieved from the query:

  1. Select the widget in which you want to use the data.
  2. Under Properties Panel, click Set from Variable or Tie to Variable.
  3. Choose the Source as todos Record and select the respective field name under Available Options.
  4. Click Save.
  5. Repeat these steps for setting the data on other widgets as well.

We also need the record data on the TaskDetailsScreen. Let's pass it inside the Navigate action as a parameter:

  1. Select the widget on which you want to define the Navigate action.
  2. From the Properties Panel, go to the Actions tab.
  3. Select Navigate action to the TaskDetailsScreen.
  4. Click + Add Action.
  5. Make sure On Tap is selected under the Type of Action.
  6. Under Parameters, click + Define. This will navigate you to the TaskDetailsScreen, from where you can define a new page parameter.
  7. Click + Add Parameter.
  8. Enter the Parameter Name as taskData.
  9. Set the Type as RecordReference and choose todos from the Record Type dropdown.
  10. Click Save. It will navigate you back to where you were defining the action.
  11. Click Pass under Parameters.
  12. Choose taskData as the Parameter from the dropdown.
  13. Select the Source as todos Record.
  14. Under Available Options, choose reference.

To display the to-do task fields on the TaskDetailsScreen, you must pass each of them as a parameter.

Define three more parameters on the TaskDetailsScreen and pass their respective values from the MyTasksScreen:

  • taskTitle (String)
  • taskDescription (String)
  • taskDate (String)
TIP: Here, we have passed each of the required fields and the record reference separately. But there's an even better way to get access to all these by just passing the entire record (gives access to all the fields and as well as to its reference). Continue reading to learn how to define it.

Similarly, tasks can also be displayed on the CompletedTasksScreen. Here, you need a second filter to filter the data according to the field is_done and show only those with the value True.

⚠️
After defining this query on the CompletedTasksScreen, you would again need to deploy the updated Firebase indexes.

Updating a todo task

There are two places where we need to perform an Update Record action:

  • On TaskDetailsScreen, Mark As Complete button should update the is_done field of a task to True.
  • On EditTaskComponent, the Update Task button should update the title, description, and date of the task to the new ones that are specified in the TextFields.

To update a task state to complete, follow the steps below:

  1. Select the Mark As Complete button present on the TaskDetailsScreen.
  2. Go to the Actions tab from the Properties Panel.
  3. Click + Add Action.
  4. Make sure On Tap is selected under the Type of Action.
  5. Select Update Record under Database actions.
  6. Choose the Source of reference as taskData.
  7. Click + Add Field.
  8. Set the field value of is_done from a specific value to True.

There's a more efficient way of passing records between pages of the app. Follow the steps below to refactor:

  1. Remove all the parameters defined on the TaskDetailsScreen.
  2. Just specify a single parameter called taskData with the Type set as Record, and Record Type as todos. Click Save.
  3. Update the Set from Variable of the widgets to use the new Source, select the respective field from the Available Options, and click Save.
  4. Update the Source of the Update Record action and use reference under Available Options.
  5. Now, go to the MyTasksScreen and select the task tile on which the Navigate action is set. Remove all the non-existing parameters and pass the taskData parameter with the Source as todos Record.

Before you can update a task from the EditTaskComponent, you need to pass the record from the TaskDetailsScreen.

  1. Select the Edit button on the TaskDetailsScreen.
  2. Go to the Bottom Sheet action defined on it.
  3. Click + Define under Parameters. This will navigate you to the EditTaskComponent.
  4. Click + Add Parameter.
  5. Enter the Parameter Name as taskData, select Type as Record, and Record Type as todos. Click Save.
  6. Now, click Pass under Parameters.
  7. Set the value of taskData as taskData (Source).

Show the current values of the task on the TextFields and also define the Date/Time Picker action:

  1. Select the TextField widgets and set their Initial Value from variable.
  2. Use taskData as the Source and the respective field under Available Options. Click Save.
  3. For displaying the date, set the Text widget value from variable.
  4. Define Date/Time Picker action with the default value set from taskData > timestamp.

To define an Update Record action on the EditTaskComponent, follow the steps below:

  1. Select the Update Task button.
  2. Go to the Actions tab from the Properties Panel.
  3. Click + Add Action.
  4. Make sure On Tap is selected under the Type of Action.
  5. Select Update Record under Database actions.
  6. Choose the Source of reference as taskData and select reference under Available Options.
  7. Click + Add Field button to specify the values of the title, description, and timestamp fields.
  8. For the title and description fields, use Value Source as From Variable, Source as Widget State, and select the respective TextField under Available Options.
  9. For the timestamp field, use Value Source as From Variable, Source as Widget State, and select Date Picked under Available Options.

Deleting a todo task

The EditTaskComponent also has an option for deleting a to-do task from the database. Follow the steps below to define the Delete Record action:

  1. Select the Delete icon button.
  2. Go to the Actions tab from the Properties Panel.
  3. Click + Add Action.
  4. Make sure On Tap is selected under the Type of Action.
  5. Select Delete Record under Database actions.
  6. Choose the Source of reference as taskData and select reference under Available Options.

Database operations in action

You can try out the CRUD operations on the database by running the app either using the Run mode or by downloading the project and running it from your system.

NOTE: To download and run the app on your system, you need to have Flutter SDK installed.

The following demonstrates the CRUD operations performed inside the app running on an iOS Simulator.

You can try out this sample app here.

Conclusion

Firestore integration with FlutterFlow makes it much simpler to manage cloud data across devices without worrying about syncing, allows real-time updates, and offers offline support out of the box.

We hope this blog post has made the basic Firestore concepts clear for you and will help you to get more confident while using Firestore in your apps.

In the next part of this blog post series, you will learn to integrate a simple search functionality to this todo app.

References

Spread the word