intl
package to format datesAppBar
and BottomAppBar
look and feel by using the BoxDecoration
and LinearGradient
widgetsIn this chapter, in Chapter 15, and in Chapter 16, you'll use techniques that you have learned in previous chapters along with new concepts and tie them together to create a production‐level mood‐journaling app. In the previous chapters, you created many projects that taught you different ways to implement specific tasks and objectives. In a production‐level app, you need to combine what you have learned to improve performance by redrawing only the widgets with data changes, pass state between pages and up the widget tree, handle the user authentication credentials, sync data between devices and the cloud, and create classes that handle platform‐independent logic between mobile and web apps.
Because Google has open‐sourced Flutter support for desktop and web apps, the Firebase backend services can be used for Flutter desktop and web apps, not just mobile. That's why in this chapter you'll learn how to develop a production‐level mobile app.
Specifically, you'll learn how to use authentication and persist data to a cloud database by using Google's Firebase (backend server infrastructure), Firebase Authentication, and Cloud Firestore. You'll learn how to create and set up a Firebase project using Cloud Firestore as the cloud database. Cloud Firestore is a NoSQL document database to store, query, and sync data with offline support for mobile and web apps. Did I say offline? Yes, I did. The ability of a mobile app to function while an Internet connection is not available is a must‐have feature that users expect. Another great feature of using Cloud Firestore is the ability to synchronize live data between devices automatically. The data syncing is fast, allowing collaboration between different devices and users. The amazing part of using these powerful features is that you don't have to deal with setting up and managing the server's infrastructure. This feature allows you to build serverless applications.
You'll configure the Firebase backend authentication provider, database, and security rules to sync data between multiple devices and platforms. To enable authentication and database services for the client‐side Flutter project, you'll add the firebase_auth
and cloud_firestore
packages. In Chapter 15 and Chapter 16 you'll learn how to implement app‐wide and local state management by using the InheritedWidget
class and maximize platform code sharing by implementing the Business Logic Component pattern. You'll use app‐wide and local state management to request data from different service classes.
Before you start configuring, let's take a look at what Firebase encompasses. Firebase consists of one platform with a multitude of products that share and work together. Firebase handles the entire backend server infrastructure connecting iOS, Android, and web apps.
Build Apps
Ensure App Quality
Grow Business
Wow, this sounds amazing, but does it cost a fortune? Actually no; Google offers the Spark Plan, which gives you free access to the majority of the products, especially Cloud Firestore. You can view the details and restrictions at https://firebase.google.com/pricing
.
In this chapter, I'll focus on using Cloud Firestore to store and sync data between devices. For general information on Firebase, you can browse https://firebase.google.com
.
What is Cloud Firestore? It stores data in documents organized in collections, similar to JSON. It can scale complex and hierarchical data by using subcollections within documents. You'll take a detailed look at it in the next section, “Structuring and Data Modeling Cloud Firestore.” It offers offline support for iOS, Android, and web apps. For the iOS and Android platforms, the offline persistence is enabled by default, but for the Web, the offline is disabled by default. To optimize querying data, it supports indexed queries with sorting and filtering. It can use transactions that automatically repeat until the task is completed. Data scaling is automatically handled for you. You use mobile SDKs to integrate different features of Firebase products into your apps.
To understand the Cloud Firestore data structure, let's compare it to a standard SQL Server database (see Table 14.1). The SQL Server database is a relational database management system (RDBMS) that supports table and row data modeling (Figure 14.1). The comparison is not a one‐to‐one but a guideline since the data structures differ.
TABLE 14.1: Data structure comparison
SQL SERVER DATABASE | CLOUD FIRESTORE |
Table | Collection |
Row | Document |
Columns | Data |
In Cloud Firestore, a collection can contain only documents. A document is a key‐value pair and can optionally point to subcollections. Documents cannot point to another document and must be stored in collections (Figure 14.2).
What is the collection's responsibility? Collections are containers for documents; they hold them the same way a folder holds pages.
What is the document's responsibility? Documents hold data that is stored as a key‐value pair similar to JSON. Documents support extra data types that JSON does not support. Each document is identified by name, and they are limited to 1MB in size (see Table 14.2).
TABLE 14.2: Cloud Firestore sample data
TYPE | VALUE |
Collection | journals |
Document | R5NcTWAaWtHTttYtPoOd |
Document data as key‐value pair | date: "2019‐0202T13:41:12.537285" mood: "Happy" note: "Great movie." uid: "F1GGeKiwp3jRpoCVskdBNmO4GUN4" |
Let's take a look at the Cloud Firestore sample data as JSON objects and in the Cloud Firestore console (Figure 14.3). Notice that the document name is a unique ID that can be automatically created by Cloud Firestore, or you can manually generate it.
{
"journals":[
{
" R5NcTWAaWtHTttYtPoOd1":{
"date": "2019-0202T13:41:12.537285",
"mood":"Happy",
"note":"Great movie."
"uid": " F1GGeKiwp3jRpoCVskdBNmO4GUN4",
}
}
]
}
Cloud Firestore supports many data types such as array, Boolean, byte, date and time, floating‐point number, geographical point, integer, map, reference, text string, and null. One of the major advantages of using Cloud Firestore is the automatic syncing of data between devices and the ability of the client app to continue working offline while the Internet is not available.
Adding security to an app is extremely important in keeping the information private and safe. Firebase Authentication provides built‐in backend services accessible from the client's SDK to support full authentication. The following is a list of the currently available authentication sign‐in providers:
Each authentication sign‐in provider is disabled by default, and you enable the providers needed for your app specs (Figure 14.4). I'll walk you through how to enable a sign‐in provider later in this chapter.
An important note on using the Anonymous provider is that if the user deletes the app from the device and reinstalls it, a new anonymous user is created and will not have access to the previous data. The data from the original anonymous user is still stored on the backend, but the client app has no way of knowing the original anonymous user ID since the app was deleted from the device. One example of using the Anonymous sign‐in provider is to allow the user free access to the app and allow a paid upgrade path to advanced features.
From your mobile app, you retrieve the user's authentication credentials from any of the available sign‐in providers. You pass these credentials to the client Firebase Authentication SDK, and the Firebase backend services verify whether credentials are valid and return a response to the client app. If the credentials are valid, you allow the user to access the data and pages in the app depending on the security rules that you will learn about in the next section, “Viewing Cloud Firestore Security Rules.”
Once a particular sign‐in provider is enabled, you use the Firebase Authentication SDK to create a Firebase User object saved on the Firebase backend. The Firebase User object has a set of properties that you can customize, except for the unique ID. You can customize the primary email address, name, and a photo URL (usually the user's photo or an avatar). By using the Firebase User object's unique ID, you bind all the data that the user has created to the ID.
To secure access to collections and documents, you implement Cloud Firestore security rules. In the previous section, you learned that you create a Firebase User object through the Firebase Authentication SDK. Once you have the Firebase User object's unique ID, you use it with Cloud Firestore security rules to secure and lock data to each user. The following code shows the security rules that you'll create for securing the Cloud Firestore database:
service cloud.firestore {
match /databases/{database}/documents {
match /journals/{document=**} {
allow read, write: if resource.data.uid == request.auth.uid;
allow create: if request.auth.uid != null;
}
}
}
The rules are editable from the database rules page on the Firebase console website. The rules consist of using the match
statements to identify documents, with the allow
expressions to control access to the documents. Every time you change the rules and save them, a history of changes is automatically created, allowing you to revert changes if needed (Figure 14.5).
Let's take a look at an example that requires rules to allow a user to read and write documents assigned to them. The first match /databases/{database}/documents
declaration tells the rules to match any Cloud Firestore Database in the project.
match /databases/{database}/documents
The second and the main part of understanding is to use the match
statement to point to the collection and expression to evaluate, as in match /journals/{document=**}
. The journals
declaration is the container name, and the expression to evaluate is document=**
(all documents for the journals
collection) wrapped inside curly brackets. Inside this particular match
, you use the allow
expression for the read
and write
privileges by using the if
statement to see whether the resource.data.uid
value equals the request.auth.uid
. The resource.data.uid
is the uid
field inside the document, and the request.auth.uid
is the logged‐in user's unique ID.
match /journals/{document=**} {
allow read, write: if resource.data.uid == request.auth.uid;
}
You can break down the rules even further by using a match
statement for each read
or write
action. For the read
rule, it can be broken into get
and list
. For the write
rule, it can be broken into create
, update
, and delete
.
// Read
allow get: if <condition>;
allow list: if <condition>;
// Write
allow create: if <condition>;
allow update: if <condition>;
allow delete: if <condition>;
The following example uses the create
rule to allow only authenticated users to add new records by checking whether the request.auth.id
value is not equal to a null
value:
allow create: if request.auth.uid != null;
You now understand how Cloud Firestore stores data and the benefits of syncing multiple devices with offline data persistence. The offline feature works by caching a copy of the app's active data, making it accessible when the device is offline. Before you can use Cloud Firestore in your app, you need to create a Firebase project.
A Firebase project is backed by the Google Cloud Platform, which allows apps to scale. The Firebase project is a container that supports sharing features such as the database, notifications, users, remote config, crash reports, and analytics (many more) between the iOS, Android, and web apps. Each account can have multiple projects, for example to separate different and unrelated applications.
You've learned how to create a Firebase project, making it possible to add the Cloud Firestore database and Firebase Authentication. You learned about the different available sign‐in methods, and in this section, you'll walk through how to implement security rules and enable an authentication sign‐in method—specifically, an email/password authentication provider.
The process of creating the mood‐journaling app spans from this chapter to Chapter 16. In this section, you'll create the base structure of the app and configure the iOS and Android projects to use Firebase Authentication and the Cloud Firestore database. You'll modify the basic look and feel of the app by using color gradients.
The goal of the mood‐journaling app is to have the ability to list, add, and modify journal entries by collecting a date, a mood, a note, and the user's ID. You'll learn to create a login page to authenticate users through the email/password Firebase Authentication sign‐in provider. The main presentation page implements a ListView
widget by using the separated
constructor sorted by DESC (descending) date, meaning last entered record first. The ListTile
widget easily formats how you'll display the List
of records. The journal entry page uses the showDatePicker
widget to select a date from a calendar, a DropdownButton
widget to select from a list of moods, and a TextField
widget to enter the note.
It's time to create the Flutter app and add the Firebase Authentication and Cloud Firestore SDKs by installing the Firebase Flutter packages. The Flutter team authors the different Firebase packages, and like other packages, the full package source code is available on the appropriate package's GitHub page.
You'll install the firebase_auth
, cloud_firestore
, and intl
packages. You'll download the Google service files (config) for iOS and Android that contain properties needed to access the Firebase products from the client app.
In the previous section, you learned how to add and configure Firebase Authentication and the Cloud Firestore database. The next step is to work on the Flutter project to customize the look and feel of the journal app.
You'll customize the app's background color by setting the MaterialApp canvasColor
property to a light shade of green. The AppBar
and BottomAppBar
customizations show a color gradient from light green to a very light shade of green, merging with the app's background color. To achieve the color effect, set the BoxDecoration gradient
property by using a LinearGradient
widget that you learned in Chapter 6, “Using Common Widgets.”
You'll need to create two classes to handle formatting dates and tracking the mood icons. The FormatDates
class uses the intl
package to format dates. The MoodIcons
class stores reference to the mood icons title
, color
, rotation
, and icon
.
In this chapter, you learned how to persist and secure data over app launches by using Google's Firebase, Firebase Authentication, and Cloud Firestore. Firebase is the infrastructure that doesn't require the developer to set up or maintain backend servers. The Firebase platform allows you to connect and share data between iOS, Android, and web apps. You configure the Firebase project with the online web console. In this chapter, you registered both the iOS and Android projects with the com.domainname.journal
package name to connect the client app to the Firebase products.
You created a Cloud Firestore database that safely stores the client app's data in a cloud database. Cloud Firestore is a NoSQL document database to store, query, and sync data with offline support for mobile and web apps. You structure and data model Cloud Firestore databases by using a collection to store documents that contain data as a key‐value pair similar to JSON.
Securing the Cloud Firestore database is done by creating Cloud Firestore security rules. The security rules consist of using the match
statements to identify documents, with the allow
expression to control access.
Firebase Authentication provides built‐in backend services that are accessible from the client's SDK, which supports full user authentication. Enabling the email/password authentication sign‐in provider allows users to register and log in to the app. By passing the user credentials (email/password) to the client's Firebase Authentication SDK, the Firebase backend services verify whether the credentials are valid and return a response to the client app.
You created the base structure of the client mood‐journaling app and connected it to the Firebase services by installing the firebase_auth
and cloud_firestore
packages. You configured the client iOS and Android projects to use Firebase. You added the GoogleService‐Info.plist
file to the iOS project and the google‐services.json
file to the Android project. The Google service files contain properties needed to access the Firebase products from the client app. You modified the base look and feel of the app by using the BoxDecoration
widget, with the gradient
property set to a LinearGradient
to create a smooth light green color gradient.
In the next chapter, you'll learn how to implement app‐wide and local state management by using the InheritedWidget
class and how to maximize platform code sharing and separation by implementing the Business Logic Component pattern. You'll use state management to implement Firebase Authentication, access the Cloud Firestore database, and implement service classes.
TOPIC | KEY CONCEPTS |
Google's Firebase | Firebase consists of one platform with many products that work together as a backend server infrastructure connecting iOS, Android, and web apps. |
Cloud Firestore | You learned how to structure and data model the Cloud Firestore database. Cloud Firestore is a NoSQL document database to store, query, and sync data with offline support. |
Firebase Authentication | Firebase Authentication provides built‐in backend services accessible from the client's SDK, supporting full authentication. |
Cloud Firestore Security Rules | To secure access to data, you learned how to implement Cloud Firestore security rules. Rules consist of using the match statements to identify documents with the allow expressions. |
Cloud Firestore collection | A collection can contain only documents. |
Cloud Firestore document | A document is a key‐value pair and can optionally point to subcollections. Documents cannot point to another document and must be stored in collections. |
firebase_auth package |
You learned how to add the firebase_auth package to enable authentication in the Flutter app. |
cloud_firestore package |
You learned how to add the cloud_firestore package to provide database storing and cloud syncing in the Flutter app. |
GoogleService‐Info.plist file |
You learned how to add the google‐services.json file to the iOS Xcode project to connect the client app to the Firebase services. |
google‐services.json file |
You learned how to add the google‐services.json file to the Android project to connect the client app to the Firebase services.For the Android project, you also learned how to modify the project and app‐level gradle files to use Firebase Authentication and Cloud Firestore. |
Flutter app base layout | You learned how to use the BoxDecoration widget by setting the gradient property to a LinearGradient list of colors to enhance the look and feel of the app. |