Firebase Realtime Database

Revision as of 15:01, 15 August 2017 by Neil (Talk | contribs)

Revision as of 15:01, 15 August 2017 by Neil (Talk | contribs)

The Firebase Realtime Database allows data to be stored securely on Google cloud servers and synchronized in realtime across all clients sharing the same database.

This chapter will introduce the key concepts and capabilities of the Firebase Realtime Database within the context of Android app development. Subsequent chapters will work through the practical application of these concepts.

An Overview of the Firebase Realtime Database

The purpose of the Realtime Database feature of Firebase is to allow data to be shared between multiple clients, where a “client” can take the form of apps running on Android and iOS mobile devices, or JavaScript running on web server.

The main goal of the system is to provide a secure, reliable and fast way to synchronize data with a minimal amount of coding effort on the part of the developer. The database system is also designed to scale to support millions of users.

The database is referred to as being “realtime” because the speed with which the data is synchronized across clients is probably as close to realtime as is currently achievable (taking into consideration the physical limitation of transmitting data over the internet and wireless connections). As will be demonstrated in later chapters, the elapsed time while a data change on one client propagates to another is visually imperceptible.

The Realtime Database also provides data persistence by storing data locally, thereby enabling the data to remain accessible even when a device is offline. When connectivity is re-established, the local data is automatically synchronized and merged with the remote data.

How is the Data Stored?

Firebase uses what is known as a NoSQL database for storing data in a Realtime Database. As the name suggest this means that data is not stored in the tables and rows found in relational database management systems (RDBMS) such as Oracle Database or Microsoft SQL Server. Nor is the data accessed using Structured Query Language (SQL) statements. Instead, the data is stored in the form of a JSON object. JSON is an acronym for JavaScript Object Notation and it defines a syntax used to transmit data in a format that is both lightweight and easy for both humans and software to read, write and understand.

JSON objects typically consist of a key/value pair, where the key uniquely identifies the object within the database and the value represents the data that is being stored. Multiple JSON objects are structured in the form of a JSON tree.

The following JSON syntax, for example, declares a very simple JSON object:

{ "name" : "Peter" }

Nesting Data

In the above example, “name” is the key and “Peter” is the value. JSON objects may also be nested. The following JSON declaration provides the structure for storing the profile information for a user:

{
  "profile" : {
    "name" : "Peter",
    "email" : {
		"address" : "[email protected]",
		"verified" : true
	}
  }
}

In this case, a JSON object has been declared with “profile” as the key. This object consists of two child nodes identified by keys which read “name” and “email”. The “email” object, in turn, contains two child objects containing the user’s email address and a value indicating whether or not the email address has been verified. Figure 19 1 illustrates this nested structure in the form of a tree diagram:


Firebase database simple tree.png

Figure 19-1


Rather like the directory structure of a computer filesystem, each node within a JSON tree is referenced by a path. Within the path each path component is represented by the key element of the corresponding node. The paths to the email address and verified nodes in the above structure, for example, would read as follows:

/profile/email/address
/profile/email/verified

Separating User Data

The user profile JSON tree outlined in the previous section would only be suitable if an app had one user since storing profile information for a second user would simply overwrite the data for the first. In order to scale the database to support multiple users, some changes need to be made to the structure. The easiest way to store data for multiple users within the same database is to make use of Firebase Authentication. All registered users are assigned a Firebase user identifier that uniquely identifies each user within the Firebase ecosystem. By using this identifier as the key for a tree node each user will, as illustrated in Figure 19-2, have a unique branch within the tree.


Firebase database complex tree.png

Figure 19-2


When a user has signed into the app using Firebase Authentication, the user ID can be obtained from the FirebaseUser object and used to construct a path to the user’s profile data. The following path, for example, represents the “verified” node for a specific user’s email address:

/profiles/<users firebase id>/email/verified

An important point to note here is that while the child nodes are implemented as key/value pairs, the user ID node contains only a key with no value. This is because the key itself is sufficient to uniquely identify the node since all user IDs are themselves unique. In this scenario, the key essentially serves as both the key and the value. The JSON structure for an entry in this database might read as follows where <user’s unique id> is replaced by the user’s actual user ID:

{
  "profile" : {
    "<user’s unique id>" : {
        "name" : "Peter",
         "email" : {
		"address" : "[email protected]",
		"verified" : true
	}
    }
  }
}

Avoiding Deep Nesting

Although nesting is a useful way both to structure a database and separate user data, care should be taken to avoid nesting too deeply wherever possible. When accessing data from a location within the tree, all of the child nodes for that location are also retrieved by the database whether that data is currently required by the app or not. Keeping the tree structure as flat as possible will ensure that the database operates at peak performance. To avoid deep nesting, consider separating nested data into separate trees and establishing relationships between the two trees using key references.

Consider, for example, an app that allows users to report bugs encountered while testing the beta version of a software application. The app will need a way to store the contact details of each user together with a list of bugs reported by that user. Each bug report will, in turn, consist of an ID that uniquely identifies the report, a brief title and a detailed explanation of how to reproduce the problem. At first glance, it might be tempting to structure the database as outlined in the partial JSON tree diagram illustrated in Figure 19-3 below:


Firebase database nested tree.png

Figure 19-3


Although this looks like a logical way to structure the data, there is a potential flaw that is unique to the way in which the Firebase Realtime Database system works. Imagine, for example, that the app includes a feature allowing all of the user’s bug reports to be listed, and that this list is only required to display the IDs and titles for those reports. The description for a particular bug report is not required unless the user taps the report in the list.

As currently structured, however, accessing the list of bugs for a particular user will also download all of the description data. If a user has filed many thousands of bug reports this will clearly download much more data than is actually needed, resulting in unnecessary bandwidth use and degraded database response times.

A better approach is to store only the bug report IDs within the user data tree, while storing the title and description information in a separate tree indexed by the same bug report ID:


Firebase database divided tree.png

Figure 19-4


As indicated by the arrow in the above tree hierarchies, the bug report ID from the user tree can be used to cross-reference the corresponding title and description data from the reports tree. Using this flat tree approach, only the necessary tree nodes are downloaded.

Supported Data Types

Having outlined the way in which data is stored in a Firebase Realtime Database, the next step is to explore the types of data that can be stored. Currently, the following data types can be stored within a realtime database:

• String

• Long

• Double

• Boolean

• Map<String, Object>

• List<Object>

• Null (empty)

• Java object

In order to be eligible for realtime database storage, the Java object must be based on a class containing a default constructor that takes no arguments and with public properties for any encapsulated data items for which storage is required. The following Java class, for example, would be eligible for storage:

import com.google.firebase.database.IgnoreExtraProperties;

@IgnoreExtraProperties
public class Profile {

    public String name;
    public String address;
    public String phone;

    public Profile() {
    }

    public Profile(String name, String address, String phone) {
        this.name = name;
        this.address = address;
        this.phone = phone;
    }
}

When a Java object is stored in a realtime database, it is converted to the equivalent JSON structure. If a Java object based on the above class were to be stored within a database, therefore, it would be structured as follows:

{
  "address" : "1 Infinite Loop",
  "name" : "Peter Wilson",
  "phone" : "555-123-1234"
}

Adding Realtime Database Support to Projects

To add Firebase realtime database support to an Android Studio project, select the Tools -> Firebase menu option and click on the Realtime Database entry in the list. Once selected, click on the Save and retrieve data link (Figure 19-5):


Firebase database enable in studio.png

Figure 19-5


Within the resulting panel, click on the Connect to Firebase button and select the Firebase Examples project to contain the app. Return to the Firebase panel and click on the Add the Realtime Database to your app button:


Firebase database add to app.png

Figure 19-6


A dialog will appear listing the changes that will be made to the project build files to add realtime database support to the project. Review these changes before clicking on the Accept Changes button:


Firebase database accept changes.png

Figure 19-7


Once these steps have been taken, the app is ready to begin writing data to the database.

Offline Data Handling

The Firebase realtime database maintains a local copy of any data currently being used by the app for use in the event that connectivity is lost by the device. For the duration of time that the device is without connectivity, all transactions performed by the app are made to the local copy of the data. When the connection is re-established, the local data will be synchronized with the remote Firebase database servers. The app will also receive any events that occurred during the offline period so that the local data can be updated to match the state of the remote database.

Summary

The Firebase Realtime Database provides a platform for storing and retrieving data on Firebase cloud servers. This provides a secure, reliable and fast way to share and synchronize data between apps that is scalable to support millions of users. The data is stored in a tree structure based on JSON, typically comprising data in key/value pairs. The database supports the storage of a range of native data types in addition to Java objects.