You're viewing the legacy docs. They are deprecated as of May 18, 2016.
These docs are for version 2.5.2 and below of the Java SDK. Go to our current docs, or see our Android migration guide.

Java Android Guide

Retrieving Data

Firebase data is retrieved by attaching an asynchronous listener to a Firebase reference. The listener will be triggered once for the initial state of the data and again anytime the data changes. This document will cover the basics of retrieving data, how Firebase data is ordered, and how to perform simple queries on Firebase data.

Getting Started

Let's revisit our blogging example from the previous article to understand how we read data from the Firebase database. Recall that the blog posts in our example app are stored at the database URL https://docs-examples.firebaseio.com/web/saving-data/fireblog/posts. To read our post data, we can do the following:

// Get a reference to our posts
Firebase ref = new Firebase("https://docs-examples.firebaseio.com/web/saving-data/fireblog/posts");

// Attach an listener to read the data at our posts reference

ref.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot snapshot) {
        System.out.println(snapshot.getValue());
    }

    @Override
    public void onCancelled(FirebaseError firebaseError) {
        System.out.println("The read failed: " + firebaseError.getMessage());
    }
});

If we run this code, we'll see an object containing all of our posts logged to system out. This method will be called anytime new data is added to our Firebase reference, and we don't need to write any extra code to make this happen.

The listener receives a DataSnapshot, which is a snapshot of the data. A snapshot is a picture of the data at a particular location in a Firebase database at a single point in time. Calling getValue() on a snapshot returns the Java object representation of the data. The possible types returned by getValue() are Boolean, String, Long, Double, Map<String, Object>, and List<Object>. If no data exists at the location, the snapshot will return null.

Notice that we used the addValueEventListener() method in our example above, which reads the entire contents of a database node. Value is one of the five different event types listed below that we can use to read data from the database.

Instead of dealing with the primitives types, we'll do what we did when saving users. We'll create a Java class that represents a Blog Post. Like last time, we have to make sure that our field names match the names of the properties in the Firebase database and give the class a default, parameterless constructor.

public class BlogPost {
  private String author;
  private String title;

  public BlogPost() {
    // empty default constructor, necessary for Firebase to be able to deserialize blog posts
  }

  public String getAuthor() {
    return author;
  }

  public String getTitle() {
    return title;
  }

}

We'll use this class in the code below when we get the value from a DataSnapshot.

Read Event Types

Value

The value event is used to read a static snapshot of the contents at a given path, as they existed at the time of the event. It is triggered once with the initial data and again every time the data changes. The event callback is passed a snapshot containing all data at that location, including child data. In our code example above, onDataChange returned all of the blog posts in our app. Every time a new blog post is added, the listener function will return all of the posts.

Now that we have a BlogPost class, we can make the code more explicit and type-safe:

// Get a reference to our posts
  Firebase ref = new Firebase("https://docs-examples.firebaseio.com/web/saving-data/fireblog/posts");

  // Attach an listener to read the data at our posts reference

  ref.addValueEventListener(new ValueEventListener() {
      @Override
      public void onDataChange(DataSnapshot snapshot) {
          System.out.println("There are " + snapshot.getChildrenCount() + " blog posts");
          for (DataSnapshot postSnapshot: snapshot.getChildren()) {
            BlogPost post = postSnapshot.getValue(BlogPost.class);
            System.out.println(post.getAuthor() + " - " + post.getTitle());
          }
      }

      @Override
      public void onCancelled(FirebaseError firebaseError) {
          System.out.println("The read failed: " + firebaseError.getMessage());
      }
  });

Child Added

The onChildAdded event is typically used when retrieving a list of items in the Firebase database. Unlike the value event which returns the entire contents of the location, the onChildAdded event is triggered once for each existing child and then again every time a new child is added to the specified path. The listener is passed a snapshot containing the new child's data.

If we wanted to retrieve the data on each post added to our blogging app, we can attach a ChildEventListener using our Firebase reference's addChildEventListener method. ChildEventListener provides methods that will be called for the onChildAdded event as well as all other child events listed.

// Get a reference to our posts
Firebase ref = new Firebase("https://docs-examples.firebaseio.com/web/saving-data/fireblog/posts");

ref.addChildEventListener(new ChildEventListener() {
    // Retrieve new posts as they are added to the database
    @Override
    public void onChildAdded(DataSnapshot snapshot, String previousChildKey) {
        BlogPost newPost = snapshot.getValue(BlogPost.class);
        System.out.println("Author: " + newPost.getAuthor());
        System.out.println("Title: " + newPost.getTitle());
    }

    //... ChildEventListener also defines onChildChanged, onChildRemoved,
    //    onChildMoved and onCanceled, covered in later sections.
});

In this example the snapshot will contain an object with an individual blog post. Because we converted our post to an object using getValue() on line 9, we have access to the post's author and title by retrieving those values from the BlogPost. In most cases we'd want to do some checking to make sure that the result is not null and actually a BlogPost.

Child Changed

The onChildChanged event is triggered any time a child node is modified. This includes any modifications to descendants of the child node. It is typically used in conjunction with the onChildAdded and onChildRemoved events to respond to changes to a list of items. The snapshot passed to the event listener contains the updated data for the child.

We can use onChildChanged to read updated data on blog posts when they are edited:

// ....

// Get the data on a post that has changed
@Override
public void onChildChanged(DataSnapshot snapshot, String previousChildKey) {
    String title = (String) snapshot.child("title").getValue();
    System.out.println("The updated post title is " + title);
}

// ....

In this last snippet, we used the DataSnapshot.child() method to access the title of the blog post and then used getValue() to get the value as a String.

Child Removed

The onChildRemoved event is triggered when an immediate child is removed. It is typically used in conjunction with onChildAdded and onChildChanged events. The snapshot passed to the event callback contains the data for the removed child.

In our blog example, we'll use onChildRemoved to print a notification about the deleted post to system out:

// ....

// Get the data on a post that has been removed
@Override
public void onChildRemoved(DataSnapshot snapshot) {
    String title = (String) snapshot.child("title").getValue();
    System.out.println("The blog post titled " + title + " has been deleted");
}

// ....

Child Moved

The onChildMoved event is used when working with ordered data, which is covered in the ordered data section.

Event Guarantees

Firebase makes several important guarantees regarding database events:

Database Event Guarantees
Events will always be triggered when local state changes.
Events will always eventually reflect the correct state of the data, even in cases where local operations or timing cause temporary differences, such as in the temporary loss of network connection.
Writes from a single client will always be written to the server and broadcast out to other users in-order.
Value events are always triggered last and are guaranteed to contain updates from any other events which occurred before that snapshot was taken.

Detaching Callbacks

Callbacks are removed by calling the removeEventListener() on our Firebase database reference:

ref.removeEventListener(originalListener);

If a listener has been added multiple times to a data location, it will be called multiple times for each event and we must detach it multiple times in order to remove it completely.

Calling removeEventListener() on a parent listener will not automatically remove listeners registered on child nodes, removeEventListener() must also be called on any child listeners to remove the callback.

Reading Data Once

In some cases it may be useful for a callback to be called once and then immediately removed. We can use addListenerForSingleValueEvent() to make this easy. It is triggered one time and then will not be triggered again..

ref.addListenerForSingleValueEvent(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot snapshot) {
        // do some stuff once
    }
    @Override
    public void onCancelled(FirebaseError firebaseError) {
    }
});

Querying Data

With Firebase queries, we can selectively retrieve data based on various factors. To construct a query, you start by specifying how you want your data to be ordered using one of the ordering functions: orderByChild(), orderByKey(), orderByValue(), or orderByPriority(). You can then combine these with five other methods to conduct complex queries: limitToFirst(), limitToLast(), startAt(), endAt(), and equalTo().

Since all of us at Firebase think dinosaurs are pretty cool, we'll use this Firebase database of dinosaur facts to demonstrate how you can query data. Here's a snippet of the dinosaur data:

{
  "lambeosaurus": {
    "height" : 2.1,
    "length" : 12.5,
    "weight": 5000
  },
  "stegosaurus": {
    "height" : 4,
    "length" : 9,
    "weight" : 2500
  }
}

We'll create a Java class to represent these dinosaur facts, so that we can easily retrieve the data from our Firebase database in a type-safe way.

public class DinosaurFacts {
  long height;
  double length;
  long weight;

  public DinosaurFacts() {
    // empty default constructor, necessary for Firebase to be able to deserialize blog posts
  }

  public long getHeight() {
    return height;
  }

  public double getLength() {
    return length;
  }

  public long getWeight() {
    return weight;
  }
}

We will use this DinosaurFacts class in some of the code samples below.

We can order data in four ways: by child key, by key name, by value, or by priority. A basic query starts with one of these ordering functions, each of which are explained below.

Ordering by a specified child key

We can order nodes by a common child key by passing that key to orderByChild(). For example, to read all dinosaurs ordered by height, we can do the following:

Firebase ref = new Firebase("https://dinosaur-facts.firebaseio.com/dinosaurs");
Query queryRef = ref.orderByChild("height");

queryRef.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot snapshot, String previousChild) {
        DinosaurFacts facts = snapshot.getValue(DinosaurFacts.class);
        System.out.println(snapshot.getKey() + " was " + facts.getHeight() + " meters tall");
    }
    // ....
});

Any node that does not have the child key we're querying on will be sorted with a value of null, meaning it will come first in the ordering. For details on how data is ordered, see the How Data is Ordered section.

Queries can also be ordered by deeply nested children, rather than only children one level down. This is useful if you have deeply nested data like this:

{
  "lambeosaurus": {
    "dimensions": {
      "height" : 2.1,
      "length" : 12.5,
      "weight": 5000
    }
  },
  "stegosaurus": {
    "dimensions": {
      "height" : 4,
      "length" : 9,
      "weight" : 2500
    }
  }
}

To query the height now, we use the full path to the object rather than a single key:

Firebase ref = new Firebase("https://dinosaur-facts.firebaseio.com/dinosaurs");
Query queryRef = ref.orderByChild("dimensions/height");
queryRef.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot snapshot, String previousChild) {
        DinosaurFacts facts = snapshot.getValue(DinosaurFacts.class);
        System.out.println(snapshot.getKey() + " was " + facts.getHeight() + " meters tall");
    }
    // ....
});

Queries can only order by one key at a time. Calling orderByChild() multiple times on the same query throws an error.

Using Indexes For Improved Performance

If you want to use orderByChild() on a production app, you should define the keys you will be indexing on via the .indexOn rule in your Security and Firebase Rules. While Firebase allows you to create these queries ad-hoc on the client, you will see greatly improved performance when using .indexOn. Read the documentation on the .indexOn rule for more information.

Ordering by key name

We can also order nodes by their keys using the orderByKey() method. The following example reads all dinosaurs in alphabetical order:

Firebase ref = new Firebase("https://dinosaur-facts.firebaseio.com/dinosaurs");
Query queryRef = ref.orderByKey();

queryRef.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot snapshot, String previousChild) {
        System.out.println(snapshot.getKey());
    }
    // ....
});

Ordering by value

We can order nodes by the value of their child keys using the orderByValue() method. Let's say the dinosaurs are having a dino sports competition and we're keeping track of their scores in the following format:

{
  "scores": {
    "bruhathkayosaurus" : 55,
    "lambeosaurus" : 21,
    "linhenykus" : 80,
    "pterodactyl" : 93,
    "stegosaurus" : 5,
    "triceratops" : 22
  }
}

To sort the dinosaurs by their score, we could construct the following query:

Firebase scoresRef = new Firebase("https://dinosaur-facts.firebaseio.com/scores");
Query queryRef = scoresRef.orderByValue();

queryRef.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot snapshot, String previousChildKey) {
      System.out.println("The " + snapshot.getKey() + " dinosaur's score is " + snapshot.getValue());
    }
    // ....
});

See the How Data is Ordered section for an explanation on how null, boolean, string, and object values are sorted when using orderByValue().

Indexing on Values for Improved Performance

If you want to use orderByValue() in a production app, you should add .value to your rules at the appropriate index. Read the documentation on the .indexOn rule for more information.

Ordering by priority

We can explicitly order nodes by priority by calling the orderByPriority() method. Details on priorities can be found in the API reference.

Complex Queries

Now that we've specified how your data will be ordered, we can use the limit or range methods described below to construct more complex queries.

Limit Queries

The limitToFirst() and limitToLast() queries are used to set a maximum number of children to be synced for a given callback. If we set a limit of 100, we will initially only receive up to 100 onChildAdded events. If we have fewer than 100 messages stored in our Firebase database, a onChildAdded event will fire for each message. However, if we have over 100 messages, we will only receive a onChildAdded event for 100 of those messages. These will be the first 100 ordered messages if we are using limitToFirst() or the last 100 ordered messages if we are using limitToLast(). As items change, we will receive onChildAdded events for items which enter the query and onChildRemoved events for items that drop out of it, so that the total number stays at 100.

Using our dinosaur facts database and orderByChild(), we can find the two heaviest dinosaurs:

Firebase ref = new Firebase("https://dinosaur-facts.firebaseio.com/dinosaurs");
Query queryRef = ref.orderByChild("weight").limitToLast(2);

queryRef.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot snapshot, String previousChild) {
        System.out.println(snapshot.getKey());
    }
    // ....
});

Our onChildAdded event will be triggered exactly two times, unless there are less than two dinosaurs stored in the database. It will also get fired for every new, heavier dinosaur that gets added to the data set.

Similarly, we can find the two shortest dinosaurs by using limitToFirst():

Firebase ref = new Firebase("https://dinosaur-facts.firebaseio.com/dinosaurs");
Query queryRef = ref.orderByChild("height").limitToFirst(2);

queryRef.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot snapshot, String previousChild) {
        System.out.println(snapshot.getKey());
    }
    // ....
});

Our onChildAdded event will be triggered exactly two times, unless there are less than two dinosaurs stored in the database. It will also get fired again if one of the first two dinosaurs is removed from the data set, as a new dinosaur will now be the second shortest.

We can also conduct limit queries with orderByValue(). If we want to create a leaderboard with the top 3 highest scoring dino sports dinosaurs, we could do the following:

Firebase scoresRef = new Firebase("https://dinosaur-facts.firebaseio.com/scores");
Query queryRef = scoresRef.orderByValue().limitToLast(3);

queryRef.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot snapshot, String previousChildKey) {
        System.out.println("The " + snapshot.getKey() + " dinosaur's score is " + snapshot.getValue());
    }
    // ....
});

Range Queries

Using startAt(), endAt(), and equalTo() allows us to choose arbitrary starting and ending points for our queries. For example, if we wanted to find all dinosaurs that are at least three meters tall, we can combine orderByChild() and startAt():

Firebase ref = new Firebase("https://dinosaur-facts.firebaseio.com/dinosaurs");
Query queryRef = ref.orderByChild("height").startAt(3);

queryRef.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot snapshot, String previousChild) {
        System.out.println(snapshot.getKey());
    }
    // ....
});

We can use endAt() to find all dinosaurs whose names come before Pterodactyl lexicographically:

Firebase ref = new Firebase("https://dinosaur-facts.firebaseio.com/dinosaurs");
Query queryRef = ref.orderByKey().endAt("pterodactyl");

queryRef.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot snapshot, String previousChild) {
        System.out.println(snapshot.getKey());
    }
    // ....
});
startAt() and endAt() are inclusive, meaning "pterodactyl" will match the query above.

We can combine startAt() and endAt() to limit both ends of our query. The following example finds all dinosaurs whose name starts with the letter "b":

Firebase ref = new Firebase("https://dinosaur-facts.firebaseio.com/dinosaurs");
Query queryRef = ref.orderByKey().startAt("b").endAt("b\uf8ff");

queryRef.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot snapshot, String previousChild) {
        System.out.println(snapshot.getKey());
    }
    // ....
});
The \uf8ff character used in the query above is a very high code point in the Unicode range. Because it is after most regular characters in Unicode, the query matches all values that start with a b.

The equalTo() method allows us to filter based on exact matches. As is the case with the other range queries, it will fire for each matching child node. For example, we can use the following query to find all dinosaurs which are 25 meters tall:

Firebase ref = new Firebase("https://dinosaur-facts.firebaseio.com/dinosaurs");
Query queryRef = ref.orderByChild("height").equalTo(25);

queryRef.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot snapshot, String previousChild) {
        System.out.println(snapshot.getKey());
    }
    // ....
});

Range queries are also useful when you need to paginate your data.

Putting it all together

We can combine all of these techniques to create complex queries. For example, we can find the name of the dinosaur that is just shorter than Stegosaurus:

final Firebase ref = new Firebase("https://dinosaur-facts.firebaseio.com/dinosaurs");

ref.child("stegosaurus").child("height").addListenerForSingleValueEvent(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot stegosaurusHeightSnapshot) {
        Long favoriteDinoHeight = stegosaurusHeightSnapshot.getValue(Long.class);

        Query queryRef = ref.orderByChild("height").endAt(favoriteDinoHeight).limitToLast(2);
        queryRef.addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot querySnapshot) {
                if (querySnapshot.getChildrenCount() == 2) {
                    // Data is ordered by increasing height, so we want the first entry
                    DataSnapshot dinosaur = querySnapshot.getChildren().iterator().next();
                    System.out.println("The dinosaur just shorter than the stegasaurus is " + dinosaur.getKey());
                } else {
                    System.out.println("The stegosaurus is the shortest dino");
                }
            }

            @Override
            public void onCancelled(FirebaseError error) {
            }
        });
    }

    @Override
    public void onCancelled(FirebaseError error) {
    }
});

How Data is Ordered

This section explains how data is ordered in the database and how to read ordered data.

orderByChild

When using orderByChild(), data that contains the specified child key will be ordered as follows:

  1. Children with a null value for the specified child key come first.
  2. Children with a value of false for the specified child key come next. If multiple children have a value of false, they are sorted lexicographically by key.
  3. Children with a value of true for the specified child key come next. If multiple children have a value of true, they are sorted lexicographically by key.
  4. Children with a numeric value come next, sorted in ascending order. If multiple children have the same numerical value for the specified child node, they are sorted by key.
  5. Strings come after numbers, and are sorted lexicographically in ascending order. If multiple children have the same value for the specified child node, they are ordered lexicographically by key.
  6. Objects come last, and sorted lexicographically by key name in ascending order.

orderByKey

When using orderByKey() to sort your data, data will be returned in ascending order by key name as follows. Keep in mind that keys can only be strings.

  1. Children with a key that can be parsed as a 32-bit integer come first, sorted in ascending order.
  2. Children with a string value as their key come next, sorted lexicographically in ascending order.

orderByValue

When using orderByValue(), children will be ordered by their value. The ordering criteria is the same as in orderByChild(), except the value of the node is used instead of the value of a specified child key.

orderByPriority

When using orderByPriority() to sort your data, the ordering of children is determined by their priority and key as follows. Keep in mind that priority values can only be numbers or strings.

  1. Children with no priority (the default) come first.
  2. Children with a number as their priority come next. They are sorted numerically by priority, small to large.
  3. Children with a string as their priority come last. They are sorted lexicographically by priority.
  4. Whenever two children have the same priority (including no priority), they are sorted by key. Numeric keys come first (sorted numerically), followed by the remaining keys (sorted lexicographically).

For more information on priorities, see the API reference.

Now that we've covered retrieving data from the database, we're ready for the next article on structuring data.

  1. 1

    Next

    Installation & Setup

  2. 2

    Next

    Understanding Data

  3. 3

    Next

    Saving Data

  4. 4

    Next

    Retrieving Data

  5. 5

    Next

    Structuring Data

  6. 6

    Next

    Understanding Security

  7. 7

    Next

    User Authentication

  8. 8

    Next

    Offline Capabilities