How to store your favorite recipes in a Firestore database by using Dart and Flutter.
In the previous article we have created a Firebase project for our recipes app and implemented the authentication with Google Sign-In. Firebase offers us many other tools that help us to develop apps and improve their quality. Today we are going to see how to include Firestore into our project. Firestore is a cloud NoSQL database.
This series of articles is a step-by-step guide of the creation of a simple recipes app in Flutter and Dart. This guide does not require any previous experience in Dart and Flutter. If you are familiar with principles of object-oriented programming, you are ready to follow this series of articles. Previous articles of this series describe how to develop the user interface of the login page and the list view of our recipes app. Furthermore, we have learned how to include Firebase into our project and authenticate users with Google by using Firebase Authentication.
You can find final results of particular articles in branches of the recipes_app repository on github. If you have already read previous articles, we are ready to continue the implementation.
Firestore — Setup
First of all, we need to setup our Firebase project for the usage of Firestore. It’s very simple. All you have to do is to follow a couple of steps described below. Before executing these steps login to Firebase and choose your project. As a first step, we are going to create a new database.
After you have chosen your Firestore project click on “Database”.
After that click on “Create database”.
It’s sufficient to create the database in test mode first. We would not do it if we run the app in production, but it is fine for development. You can find more about Cloud Firestore security rules here.
Cool. We have successfully created a Firestore database for our app. Congratulations. Now, let’s add data of our recipes to our brand new database.
Documents and Collections
A Firestore database is based on collections and documents. Each collection contains several documents. Each document contains multiple key-value pairs. Since Firestore is a NoSQL database we are not going to work with columns and rows.
Click on “Add collection” to create a new collection. We are going to call it “recipes”.
Let’s move to the next step and add data to the created collection. So far we have been getting data by using the method
getRecipes. The method
getRecipes returns a list of
Recipe objects based on hardcoded data in lib/utils/store.dart. Now, we are going to add recipes to our brand new Firestore collection “recipes” as documents. Each document is going to represent a recipe.
Here is an overview of the fields that our recipe documents are going to contain:
- Document ID — we are going to map the document ID on the
idproperty in Recipe class
- type (number) — this field is going to reference the property
typein the Recipes class. We’re going to use 0 for
RecipeType.foodand 1 for
- name (string) — it is going to reference to the
nameproperty in the Recipe class
- duration (number) — we are going to map this field to the
durationproperty in the
Recipeclass and create a
Durationobject by using its value as soon as we deserialize data from Firestore
- ingredients (array) — an array of string fields that is going to be used for the
- preparation (array) — it is going to map the
preparationproperty in the
- image (string) — we are going to use its value for the
On the screenshots below you can see an example of creating a new document for a recipe.
Click on “Save” to finish the creation of the document.
Now repeat these steps for other recipes. Feel free to add as many recipes as you wish.
Nice. Now, after we have added data of all recipes we are ready to start with the implementation.
We are going to use the
cloud_firestore package as Dart library for the implementation today.
Let’s add the required package to our project by including it into the dependencies section in pubspec.yaml first.
Now, run flutter packages get to get the new package and to be able to use it as a Dart library.
In order to be able to work with data from Firestore and
Recipe objects we are going to implement a constructor
fromMap in the class
Recipe. The method
fromMap is going to deserialize data that we are going to receive from Firestore and initialize a new
Let’s add the constructor
fromMap to the class
Recipe in recipe.dart.
In the previous article we have learned how to store app’s state and pass the state down the widget tree using
InheritedWidget. So far we have stored data of the state in a
State object. By now, the
State class contains properties
In order to store users’ favorites add a new property
favorites of data type
List<String> to the State class.
Since data of our recipes is stored in Firestore we do not need the
getRecipes method in lib/utils/store.dart anymore.
Update Firestore Database
Delete the code in store.dart and put the implementation of the method
updateFavorites that follows into the file store.dart.
Why do we need the method
Here is what the new method is going to do:
- Create a collection “users” and a document named by the current user’s ID that contains an array “favorites”. The array “favorites” is going to be a list of string values representing recipe IDs.
- Add the passed recipe ID to “favorites” of current user if the collection and document already exist and the collection “favorites” doesn’t contain given recipe ID.
- Delete the recipe ID in the another case (the collection “favorites” already contains the passed recipe ID).
updateFavorites is going to contain the entire logic of adding or deleting entries of users’ favorites.
Here is the implementation:
As next, we are going to add a new method called
getFavorites to the class
We are going to use
getFavorites to get user’s favorites on the initialization of the app’s state. As soon as the current user is signed-in with Google we are going to load his or her favorites.
In the code that follows you can see the implementation of the new method
getFavorites and how we use it within the method
Let’s move to the implementation of the list view in the widget of the home screen. There are a couple of changes we need to implement.
Since the method
_HomeScreenState does not update data in Firestore yet, we need to change it, too. We are going to implement the following scenario in
- Update the list of user’s favorites in Firestore by using the method
- If the method
updateFavoriteshas been successfully executed, update the state of the app
Take a look at the implementation.
In the last step we are going to change the method
_buildTabsContent in home.dart so we are able to get recipes’ data from our cloud database.
We are going to change parameters of the private method
_buildRecipes first. Change parameters of this method to optional parameters
RecipeType recipeType and
List<String> ids. Notice the syntax that we are using for optional named parameter in the code that follows. An optional named parameters are wrapped by curly brackets.
At the beginning of the method
_buildRecipes we define a reference to the source of recipes’ data. In our case it is an CollectionReference to the previously created collection “recipes”. After that we declare a new
Stream<QuerySnapshot> object called
stream. Depending on whether an argument for the parameter
recipeType has been passed we define
stream so we get recipes of passed type or we get all recipes from the stream later on.
If we take a look at the implementation below we can see that the
ListView.builder that we have been using before has been replaced by
StreamBuilder listens to the stream and returns a
What about the parameter
ids? We are going to use
ids to get
RecipeCard widgets only for recipes contained in the list of favorites which are being stored in the favorites array in the
RecipeCard is being returned by the
StreamBuilder we check if
ids is set. If so, we check if the recipe’s ID is contained in the list passed to parameter
Here is the code:
Now, delete unused variables in
Well done! We have finished the implementation.
Let me summarize what we have learned today.
We have learned how to:
- create a Firestore database, add data by creating new collection and documents
- deserialize data from a
Mapobject by implementing a constructor
- create, update and delete data in Firestore by using Transaction
- use optional named parameters by using curly brackets
- use StreamBuilder in order to create a
In the next article we are going to implement a settings widget that we are going to use in the settings view and finish the development for this series of articles.
Thank you for reading. I wish you happy fluttering!