Reading Data from Firebase

Firebase is a real-time database, so data is never read synchronously. Instead, you read data by attaching a callback to a Firebase reference as shown:

var dataRef = new Firebase('https://SampleChat.firebaseIO-demo.com/users/fred/name/first');
dataRef.on('value', function(snapshot) {
  alert('fred’s first name is ' + snapshot.val());
});

NSString* url = @"https://SampleChat.firebaseIO-demo.com/users/fred/name/first";
Firebase* dataRef = [[Firebase alloc] initWithUrl:url];
[dataRef observeEventType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
    NSLog(@"fred's first name is: %@", snapshot.value);
}];

String url = "https://SampleChat.firebaseIO-demo.com/users/fred/name/first";
Firebase dataRef = new Firebase(url);
dataRef.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot snapshot) {
        System.out.println("Fred's first name is " + snapshot.getValue());
    }

    @Override
    public void onCancelled() {
        System.err.println("Listener was cancelled");
    }
});

There are a few key things to understand here:

  1. All reads are done through asynchronous callbacks. If the referenced data is already cached, your callback will be called immediately, but if this is the first time the data was accessed by this client, Firebase will need to request the data from the Firebase servers first.

  2. Callbacks are triggered both for the initial state of your data and again any time data changes. In the above example, our callback will be called again if Fred's first name ever changes.

  3. Callbacks receive snapshots of data. A snapshot is a picture of the data at a particular Firebase location at a single point in time. It contains all of the data at that location, including any child data. If you want to convert this data to a native format (such as a JavaScript object on the web or a Dictionary in Objective-C), you must do so explicitly.

  4. Firebase is intelligent about aggregating callbacks. Firebase ensures that only the minimum required dataset is loaded from the server, and the calling of callbacks and generation of snapshots is extremely efficient. As a result, you should feel comfortable attaching many callbacks and having multiple callbacks of different types attached to the same location.

  5. Events that are triggered on your client do not always correspond exactly with the write operations that were performed on other clients. For example, if two other clients were to set the same piece of data at approximately the same time, there is no guarantee that two events will be triggered on your local client. Depending on the timing, those two changes could be aggregated into a single local event. Regardless, eventually all clients will have a consistent view of the data, even if the events triggered in the process may differ from client-to-client.

Firebase Event Types

There are 5 different event types for which you can attach callbacks:

  1. Value
  2. Child Added
  3. Child Changed
  4. Child Removed
  5. Child Moved

Value

The 'value' event is used to read the entire contents of a Firebase location. It is triggered once with the initial data and again every time the data changes. Your event callback is passed a snapshot containing all data at that location, including child data. If no data exists, the event will trigger with an empty snapshot.

For example, the following code snippet reads all of the data for user 'julie' in the SampleChat Firebase:

var julieRef = new Firebase('https://SampleChat.firebaseIO-demo.com/users/julie/');
julieRef.on('value', function(snapshot) {
  if(snapshot.val() === null) {
    alert('User julie does not exist.');
  } else {
    var firstName = snapshot.val().name.first;
    var lastName = snapshot.val().name.last;
    alert('User julie’s full name is: ' + firstName + ' ' + lastName);
  }
});

Firebase* julieRef = [[Firebase alloc] initWithUrl:@"https://SampleChat.firebaseIO-demo.com/users/julie"];
[julieRef observeEventType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
  if(snapshot.value == [NSNull null]) {
    NSLog(@"User julie doesn't exist");
  } else {
    NSString* firstName = snapshot.value[@"name"][@"first"];
    NSString* lastName = snapshot.value[@"name"][@"last"];
    NSLog(@"User julie's full name is: %@ %@", firstName, lastName);            
  }
}];    

Firebase julieRef = new Firebase("https://SampleChat.firebaseIO-demo.com/users/julie/");
julieRef.addValueEventListener(new ValueEventListener() {
     @Override
     public void onDataChange(DataSnapshot snapshot) {
         Object value = snapshot.getValue();
         if (value == null) {
             System.out.println("User julie doesn't exist");
         } else {
             String firstName = (String)((Map)value).get("first");
             String lastName = (String)((Map)value).get("last");
             System.out.println("User julie's full name is: " + firstName + " " + lastName);
         }
     }

     @Override
     public void onCancelled() {
         System.err.println("Listener was cancelled");
     }
 });

Value events are always fired after any other callbacks are triggered for a given location, and they are only triggered after all of the data has finished loading. This makes Value events ideal for detecting when your "initial state" has loaded. For example, if you want to place a "Loading..." message in your app that disappears once the app has loaded its data, you could use a Value event to determine that loading is complete. Note that adding a Value event callback to a location that already has other callbacks attached is an extremely efficient operation, so you should feel comfortable doing this regularly in your app.

Child Added

The Child Added event is typically used when retrieving a list of items (e.g. chat messages) in Firebase. Unlike 'value' which fires for the entire contents of the location, 'child_added' fires once for each immediate child and continues to trigger as new children are added. Your event callback is passed a snapshot containing the new child's data.

Here is an example use of the Child Added event:

var usersRef = new Firebase('https://SampleChat.firebaseIO-demo.com/users/');
usersRef.on('child_added', function(snapshot) {
  var userName = snapshot.name(), userData = snapshot.val();
  alert('User ' + userName + ' has entered the chat');
});

Firebase* usersRef = [[Firebase alloc] initWithUrl:@"https://SampleChat.firebaseIO-demo.com/users/"];
[usersRef observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) {
  NSString *userName = snapshot.name;
  NSDictionary *userData = snapshot.value;
  NSLog(@"User %@ has entered the chat", userName);
}];

Firebase usersRef = new Firebase("https://SampleChat.firebaseIO-demo.com/users/");
usersRef.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot snapshot, String previousChildName) {
        String userName = snapshot.getName();
        GenericTypeIndicator<Map<String, Object>> t = new GenericTypeIndicator<Map<String, Object>>() {};
        Map<String, Object> userData = snapshot.getValue(t);
        System.out.println("User " + userName + " has entered the chat");
    }

    @Override
    public void onChildChanged(DataSnapshot snapshot, String previousChildName) {

    }

    @Override
    public void onChildRemoved(DataSnapshot snapshot) {

    }

    @Override
    public void onChildMoved(DataSnapshot snapshot, String previousChildName) {

    }

    @Override
    public void onCancelled() {

    }
});

Child Changed

The Child Changed event is triggered any time a child (or one of its descendants) changes. It is typically used in conjunction with Child Added and Child Removed to respond to changes to a list of items. The snapshot passed to the event callback contains the updated data for the child.

Here is an example use of the Child Changed event:

var usersRef = new Firebase('https://SampleChat.firebaseIO-demo.com/users/');
usersRef.on('child_changed', function(snapshot) {
  var userName = snapshot.name(), userData = snapshot.val();
  alert('User ' + userName + ' now has a name of ' + userData.name.first + ' ' + userData.name.last);
});

Firebase* usersRef = [[Firebase alloc] initWithUrl:@"https://SampleChat.firebaseIO-demo.com/users/"];
[usersRef observeEventType:FEventTypeChildChanged withBlock:^(FDataSnapshot *snapshot) {
  NSString *userName = snapshot.name;
  NSDictionary *userData = snapshot.value;
  NSLog(@"User %@ now has a name of %@ %@", userName, userData[@"name"][@"first"], userData[@"name"][@"last"]);
}];    

Firebase usersRef = new Firebase("https://SampleChat.firebaseIO-demo.com/users/");
usersRef.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot snapshot, String previousChildName) {

    }

    @Override
    public void onChildChanged(DataSnapshot snapshot, String previousChildName) {
        String userName = snapshot.getName();
        String firstName = (String)snapshot.child("name/first").getValue();
        String lastName = (String)snapshot.child("name/last").getValue();
        System.out.println("User " + userName + " now has a name of " + firstName + " " + lastName);
    }

    @Override
    public void onChildRemoved(DataSnapshot snapshot) {

    }

    @Override
    public void onChildMoved(DataSnapshot snapshot, String previousChildName) {

    }

    @Override
    public void onCancelled() {

    }
});

Child Removed

The Child Removed event is triggered when an immediate child is removed.
It's typically used in conjunction with Child Added and Child Changed. The snapshot passed into the event callback contains the data for the removed child.

Here is an example use of the Child Removed event:

var usersRef = new Firebase('https://SampleChat.firebaseIO-demo.com/users/');
usersRef.on('child_removed', function(snapshot) {
  var userName = snapshot.name(), userData = snapshot.val();
  alert('User ' + userName + ' has left the chat.');
});

Firebase* usersRef = [[Firebase alloc] initWithUrl:@"https://SampleChat.firebaseIO-demo.com/users/"];
[usersRef observeEventType:FEventTypeChildRemoved withBlock:^(FDataSnapshot *snapshot) {
  NSString *userName = snapshot.name;
  NSDictionary *userData = snapshot.value;
  NSLog(@"User %@ has left the chat.", userName);
}];

Firebase usersRef = new Firebase("https://SampleChat.firebaseIO-demo.com/users/");
usersRef.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot snapshot, String previousChildName) {

    }

    @Override
    public void onChildChanged(DataSnapshot snapshot, String previousChildName) {

    }

    @Override
    public void onChildRemoved(DataSnapshot snapshot) {
        String userName = snapshot.getName();
        GenericTypeIndicator<Map<String, Object>> t = new GenericTypeIndicator<Map<String, Object>>() {};
        Map<String, Object> userData = snapshot.getValue(t);
        System.out.println("User " + userName + " has left the chat.");
    }

    @Override
    public void onChildMoved(DataSnapshot snapshot, String previousChildName) {

    }

    @Override
    public void onCancelled() {

    }
});

Child Moved

The Child Moved event is used when working with ordered data. See Ordered Data for details.

Reading Data Once

In some special cases, it may be useful for a callback to be called once and then immediately removed. We've created a helper function to make this easy:

dataRef.once('value', function(data) {
  // do some stuff once
});

[dataRef observeSingleEventOfType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
  // do some stuff once
}];

dataRef.addListenerForSingleValueEvent(new ValueEventListener() {
     @Override
     public void onDataChange(DataSnapshot snapshot) {
         // Do some stuff once
     }

     @Override
     public void onCancelled() {
         System.err.println("Listener was cancelled");
     }
});

This is equivalent to:

var func = function(data) {
  // do some stuff
  ...

  // Remove the callback
  dataRef.off('value', func);
}
dataRef.on('value', func);

__block FirebaseHandle handle = [dataRef observeEventType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
  // do some stuff
  ...

  // Remove the callback
  [dataRef removeObserverWithHandle:handle];
}];

dataRef.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot snapshot) {
        // Do some stuff
        ...

        // Remove the callback
        dataRef.removeEventListener(this);
    }

    @Override
    public void onCancelled() {
        System.err.println("Listener was cancelled");
    }
});

Using a Cancel Callback

A cancel callback is an optional callback that will be called if your event subscription is ever canceled because your client does not have permission to read from a Firebase location (or it had permission but has now lost it). This callback will be passed an Error object indicating why the failure occurred.

var fredRef = new Firebase('https://SampleChat.firebaseIO-demo.com/users/fred/name/first');
fredRef.on('value', function(snapshot) {
    // Read succeeds
    alert("We have permission.");
}, function(err) {
    // Read fails
    alert("We do not have permission.");
});

Firebase* fredRef = [[Firebase alloc] initWithUrl:@"https://SampleChat.firebaseIO-demo.com/users/fred/name/first"];
[fredRef observeEventType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
  // Read succeeds.
  NSLog(@"We have permission.");
} withCancelBlock:^(FDataSnapshot *snapshot) {
  // Read fails.   
  NSLog(@"We do not have permission.");
}];

Firebase fredRef = new Firebase("https://SampleChat.firebaseIO-demo.com/users/fred/name/first");
fredRef.addValueEventListener(new ValueEventListener() {
     @Override
     public void onDataChange(DataSnapshot snapshot) {
        // Read succeeds
        System.out.println("We have permission.");
     }

     @Override
     public void onCancelled() {
        // Read fails
         System.err.println("We do not have permission.");
     }
 });

Detaching Callbacks

In JavaScript, callbacks are removed by specifying the event type and the callback function you would like removed. In Objective-C, callbacks are removed by specifying the handle you would like removed (handles are returned when the callback is initially attached). In Java, callbacks are removed by removing the specific listener that was attached.

dataRef.off('value', someCallback);

[dataRef removeObserverWithHandle:someCallbackHandle];

dataRef.removeEventListener(someValueOrChildEventListener);

Note that if a callback has been added multiple times to a data location, it will be called multiple times for each event, and you must detach it multiple times in order to remove it completely.

If you would like to remove all callbacks at a location, you can do so as shown:

//Remove all Value callbacks
dataRef.off('value');

//Remove all callbacks of any type
dataRef.off();

//Remove all callbacks at a location
[dataRef removeAllObservers];

// In Java, each listener must be removed explicitly.

Providing a Context in JavaScript

In the JavaScript SDK, an optional context argument can be provided which will be used as 'this' when calling your complete and cancel callbacks.

var fredRef = new Firebase('https://SampleChat.firebaseIO-demo.com/users/fred/name/first');
fredRef.once('value', function(snapshot) {
    if (snapshot.val() === this.username) {
        // Value matches "Fred"
        alert("Remote User matches Local User.");   
    }else{
        // Value doesn't match "Fred"
        alert("Remote User does not match Local User.");      
    }
}, {username: "Fred"});

Guarantees

Firebase makes several important guarantees regarding events:

  1. Events will always be triggered when local state changes.

  2. For local write operations, events will be triggered immediately.

  3. Events will always eventually reflect the correct state of the data, regardless of whether or not timing or local operations cause temporary differences between the local state and the state of the data on the Firebase servers.

  4. Value events will only be triggered after all relevant data has been loaded from the server.

  5. Writes from a single client will always be written to the server and broadcast out to other users in-order.


Have a suggestion to improve the documentation on this page? Tell us!