Changes

Working with Firebase Realtime Database Lists

10,652 bytes added, 18:40, 15 August 2017
Created page with "So far we have only looked at the reading and writing of individual data items within the Firebase Realtime Database. Firebase also provides support for working with lists of..."
So far we have only looked at the reading and writing of individual data items within the Firebase Realtime Database. Firebase also provides support for working with lists of data within the database tree. This system allows items to be appended to and removed from a list, and also for query operations to be performed to extract one or more items from a list based on filtering and matching criteria.

The key elements of Firebase database list handling are the push() method, the Query class, child and value event listeners and a range of filtering options.

This chapter will provide an overview of working with lists with the Firebase Realtime Database in preparation for the tutorial outlined in a later chapter entitled [[A Firebase Realtime Database List Data Tutorial]].

== The push() Method ==

As with all realtime database implementations, an app must first obtain a database reference instance before working with lists:

<pre>
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference dbRef = database.getReference("/data");
</pre>

The push() method of the database reference allows child nodes to be appended to a list of items in the database. Each time the push() method is called, it creates a new node at the path location of the database reference instance and assigns it an auto-generated key. This key is unique and includes timestamp information so that all keys in the list are automatically ordered based on the time of creation.

Once the push method has created a new child, it returns a DatabaseReference object initialized with the path to that child. Calling the getKey() method on this reference will return the auto-generated key which may then be used to store a corresponding value.

The following code, for example, uses the push() method to add a new child at the path stored within the database reference instance:

<pre>
DatabaseReference newChildRef = dbRef.push();
</pre>

With access to the database reference for the new child, the corresponding key may be obtained and used to store a value as follows:

<pre>
String key = newChildRef.getKey();
dbRef.child(key).setValue("An Item");
</pre>

When executed, the database tree created by this code will resemble that shown below:

[[File:]]

Figure 23-1

The code to perform the push operation may be simplified in a number of ways. One option is to extract the key directly from the database reference:

<pre>
String key = dbRef.push().getKey();
dbRef.child(key).setValue("An Item");
</pre>

Alternatively, the setValue() method may be called on the database reference instance for the new child:

<pre>
DatabaseReference newChildRef = dbRef.push();
newChildRef.setValue("An Item");
</pre>

Each time the push() method is called, it will create a new node with a unique key which may be used to store a data value. Figure 23 2, for example, shows multiple children appended to the list using this technique:


[[File:]]

Figure 23-2


Using Firebase to generate unique keys in this way is also of particular use when an app has multiple users creating child nodes at the same path in the tree. Since each key is guaranteed to be unique, there is no risk of multiple users attempting to save data using the same key.

== Listening for Events ==

In addition to being able to append children to a database list, it is also important for an app to receive notifications when changes occur within the list. This can be achieved by setting up event listeners. Previous chapters have already outlined the use of the value event listener. As the name suggests, a value event listener is triggered when the value stored within a database tree node changes. In addition to value event listeners, working with Firebase Realtime Database lists will typically also require the use of child event listeners. These listeners notify the app when child nodes are added, deleted or moved within a list.

Child event listeners are created as instances of the ChildEventListener class and added via a call to the addChildEventListener() method of the database reference instance.

The following callback methods must be implemented within the child event listener:

• '''onChildAdded()''' – This method is called each time a new child is added to a list. When the method is called it is passed a DataSnapshot object containing the newly added data.

• '''onChildChanged()''' – Called when the data assigned to a node in the list changes, or new children are added to that node. When called, the method is passed a DataSnapshot instance containing the modified data.

• '''onChildRemoved()''' – When a child node is removed from a list, this method is called and passed a DataSnapshot object containing the data for the deleted node.

• '''onChildMoved()''' – This method is called when any changes result in alterations to the order of the items in a list.

The following code declares and then adds a child event listener to a database reference:

<pre>
ChildEventListener childListener = new ChildEventListener() {

@Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {

}

@Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {

}

@Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {

}

@Override
public void onChildRemoved(DataSnapshot dataSnapshot) {

}

@Override
public void onCancelled(DatabaseError databaseError) {

}
};

dbRef.addChildEventListener(childListener);
</pre>

When no longer required, child event listeners should be removed from the database reference:

<pre>
dbRef.removeEventListener(childListener);
<pre>

== Performing List Queries ==

The Firebase Query class allows apps to retrieve items from a database list based on specified criteria. A range of query options are available including retrieving items in a list in a particular order, or retrieving only certain items based on data matching. Options are also available to control the number and position of the list items to be retrieved (for example the first three or last four items in a list).

Once a query has been configured, an event listener is attached which will be called when the operation is complete. The listener will be passed a DataSnapshot object containing the data result of the query.

Consider, for example, a list structured as illustrated in Figure 23 3 which consists of keys generated by the push() method, each of which has a child node consisting of a key/value pair:


[[File:]]

Figure 23-3


The items are listed in the order that they were appended to the list, starting with the oldest entry at the top. To retrieve these items from the list in chronological order, the query would need to be configured using order by key (since the push() method included timestamp information in the keys as items were appended to the list). The code to initialize the query would, therefore, read as follows:

<pre>
Query query = dbRef.orderByKey();
query.addListenerForSingleValueEvent(queryValueListener);
</pre>

Note the event listener has been added using the single value event listener method. Since queries are usually onetime events this is more convenient than having to remove the listener after the query completes.

The queryValueListener referenced in the above code could be implemented as follows:

<pre>
ValueEventListener queryValueListener = new ValueEventListener() {

@Override
public void onDataChange(DataSnapshot dataSnapshot) {

Iterable<DataSnapshot> snapshotIterator = dataSnapshot.getChildren();
Iterator<DataSnapshot> iterator = snapshotIterator.iterator();

while (iterator.hasNext()) {
DataSnapshot next = (DataSnapshot) iterator.next();
Log.i(TAG, "Value = " + next.child("name").getValue());
}
}

@Override
public void onCancelled(DatabaseError databaseError) {

}
};
</pre>

The onDataChanged() callback method is passed a DataSnapshot object containing all of the children that matched the query. As implemented above, the method iterates through the children, outputting the name value for each node to the logcat console. Assuming the data list shown in Figure 23 3 above, the resulting output would read as follows:

<pre>
Ethan
Alex
Dan
Chris
Bill
</pre>

To retrieve the data ordered based on the values, an order by child query needs to be performed referencing the “name” key in the child nodes:

<pre>
Query query = dbRef.orderByChild("name");
</pre>

When executed, the names will be retrieved and output in alphabetical order:

<pre>
Alex
Bill
Chris
Dan
Ethan
</pre>

The items matching specified criteria may be retrieved using the equalTo() filter. The following code will extract only those list items that match the name “Chris”:

<pre>
Query query = dbRef.orderByChild("name").equalTo("Chris");
</pre>

Segments of the list may also be retrieved using the startAt() and endAt() filters. These filters may be used independently, or as in the following example, combined to specify both start and end points:

<pre>
Query query = dbRef.orderByChild("name").startAt("Bill").endAt("Dan");
</pre>

The above query, when performed on the example name list, will generate the following output (note that ordering by child is still being used so the results are presented in alphabetical order):

<pre>
Bill
Chris
Dan
</pre>

Specific numbers of list items may be retrieved using the limitToFirst() and limitToLast() filters. Unlike the start and end filters, these filters may not be combined into a single query operation. In the following example, only the first two list entries will be retrieved:

<pre>
Query query = dbRef.orderByChild("name").limitToFirst(2);
</pre>

== Querying and Indexes ==

If an app is expected to make frequent database queries it will be important to declare appropriate indexing rules to maintain the performance of the database. This is achieved using the .onIndex rule type as outlined in the Firebase Realtime Database Rules chapter of this book.

== Summary ==

In addition to the reading and writing of individual data items, Firebase also includes support for working with data in list form. When working with lists, new items are appended to a list using the push() method of the DatabaseReference class. This method creates a child node with an auto-generated key into which data may be written. The Query class, when combined with value and child event listeners allows for a wide range of querying and filtering operations to be performed on lists of data stored using the Firebase Realtime Database.