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

Saving Data

In this document we'll cover the four methods for writing data to the Firebase database: setValue(), updateChildren(), push(), and our runTransaction() feature.

Ways to Save Data

setValue( ) Write or replace data to a defined path, like messages/users/<username>
updateChildren( ) Update some of the keys for a defined path without replacing all of the data
push( ) Add to a list of data in your Firebase database. Every time you call push() Firebase generates a unique ID, like messages/users/<unique-user-id>/<username>
runTransaction( ) Use our transactions feature when working with complex data that could be corrupted by concurrent updates

Saving Data with setValue()

The basic write operation is setValue() which saves data to the specified Firebase reference, replacing any existing data at that path. To understand setValue(), we'll build a simple blogging app. The data for our app will be stored at this Firebase reference:

Firebase ref = new Firebase("https://docs-examples.firebaseio.com/android/saving-data/fireblog");

Let's start by saving some user data to the Firebase database. We'll create our own User class and create an object to store in the database. We'll create a reference to the location of our user data and call setValue() to save the User object. We can pass our own custom Java object to setValue(), provided that the class that defines it has a default constructor that takes no arguments and public getters for the properties to be assigned:

public class User {
    private int birthYear;
    private String fullName;

    public User() {}

    public User(String fullName, int birthYear) {
        this.fullName = fullName;
        this.birthYear = birthYear;
    }

    public long getBirthYear() {
        return birthYear;
    }

    public String getFullName() {
        return fullName;
    }
}

Firebase alanRef = ref.child("users").child("alanisawesome");

User alan = new User("Alan Turing", 1912);

alanRef.setValue(alan);

We're using a User object to save data to the database. The contents of the User are automatically mapped to child locations in a nested fashion. Now if we navigate to the URL https://docs-examples.firebaseio.com/android/saving-data/fireblog/users/alanisawesome/fullName, we'll see the value "Alan Turing".

You can also save data directly to a database location:

//Referencing the child node using a .child() on it's parent node
alansRef.child("fullName").setValue("Alan Turing");
alansRef.child("birthYear").setValue(1912);

The above two examples - writing User objects and writing the separate properties to specific database locations - will result in the same data being saved to your database:

{
  "users": {
    "alanisawesome": {
      "birthYear": "1912",
      "fullName": "Alan Turing"
    }
  }
}
Using setValue() will overwrite the data at the specified location, including any child nodes.

You can pass setValue() types that correspond to the the types available in JSON: String, Long, Double, Boolean, Map<String, Object> and List<Object>. With these types we can pass arbitrarily complex data structures, for example a Map may contain another Map as a value. We can duplicate the functionality above using Map instead of custom objects:

Firebase usersRef = ref.child("users");

Map<String, String> alanisawesomeMap = new HashMap<String, String>();
alanisawesomeMap.put("birthYear", "1912");
alanisawesomeMap.put("fullName", "Alan Turing");

Map<String, Map<String, String>> users = new HashMap<String, Map<String, String>>();
users.put("alanisawesome", alanisawesomeMap);

usersRef.setValue(users);

This will write the same JSON structure in the database as before, but using a User object is more readable and easier to maintain.

Note that passing null to setValue() will remove the data at the specified location.

Updating Specific Fields with updateChildren()

If you want to write to specific children of a node at the same time without overwriting other child nodes, you can use the updateChildren() method as shown below:

Firebase alanRef = usersRef.child("alanisawesome");
Map<String, Object> nickname = new HashMap<String, Object>();
nickname.put("nickname", "Alan The Machine");
alanRef.updateChildren(nickname);

This will update Alan's data to include a nickname. If we had used setValue() here instead of updateChildren(), it would have deleted both fullName and birthYear from our alanRef. Note that passing null in a map will delete the data at that location in the database.

Firebase also supports multi-path updates. This means that updateChildren() can now update values at multiple locations in your Firebase database at the same time, a powerful feature which allows helps you denormalize your data. Using multi-path updates, we can add nicknames to both Alan and Grace at the same time:

Map<String, Object> nicknames = new HashMap<String, Object>();
nickname.put("alanisawesome/nickname", "Alan The Machine");
nickname.put("gracehop/nickname", "Amazing Grace");

usersRef.updateChildren(nicknames);

After this update, both Alan and Grace have had their nicknames added:

{
  "users": {
    "alanisawesome": {
      "date_of_birth": "June 23, 1912",
      "full_name": "Alan Turing",
      "nickname": "Alan The Machine"
    },
    "gracehop": {
      "date_of_birth": "December 9, 1906",
      "full_name": "Grace Hopper",
      "nickname": "Amazing Grace"
    }
  }
}

Note that trying to update objects by writing objects with the paths included will result in different behavior. Let's take a look at what happens if we instead try to update Grace and Alan this way:

Map<String, Object> alanNickname = new HashMap<String, Object>();
alanNickname.put("nickname", "Alan The Machine");

Map<String, Object> graceNickname = new HashMap<String, Object>();
graceNickname.put("nickname", "Amazing Grace");

Map<String, Object> nicknames = new HashMap<String, Object>();
nickname.put("alanisawesome", alanNickname);
nickname.put("gracehop", graceNickname);

usersRef.updateChildren(nicknames);

This results in different behavior, namely overwriting the entire /users node:

{
  "users": {
    "alanisawesome": {
      "nickname": "Alan The Machine"
    },
    "gracehop": {
      "nickname": "Amazing Grace"
    }
  }
}
Updating Nested Data

Given a single key path like alanisawesome, updateChildren() only updates data at the first child level, and any data passed in beyond the first child level is a treated as a setValue() operation. Multi-path behavior allows longer paths (like alanisawesome/nickname) to be used without overwriting data. This is why the first example differs from the second example.

Adding a Completion Callback

If you'd like to know when your data has been committed, you can add a completion listener. Both setValue() and updateChildren() take an optional completion listener that is called when the write has been committed to the database. If the call was unsuccessful for some reason, the listener will be passed an error object indicating why the failure occurred:

ref.setValue("I'm writing data", new Firebase.CompletionListener() {
    @Override
    public void onComplete(FirebaseError firebaseError, Firebase firebase) {
        if (firebaseError != null) {
            System.out.println("Data could not be saved. " + firebaseError.getMessage());
        } else {
            System.out.println("Data saved successfully.");
        }
    }
});

Saving Lists of Data

When creating lists of data, it is important to keep in mind the multi-user nature of most applications and adjust your list structure accordingly. Expanding on our example above, let's add blog posts to our app. Your first instinct might be to use child() to store children with auto-incrementing integer indexes, like the following:

ANTIPATTERN: This is not a recommended practice
// NOT RECOMMENDED - use push()!
{
  "posts": {
    "0": {
      "author": "gracehop",
      "title": "Announcing COBOL, a New Programming Language"
    },
    "1": {
      "author": "alanisawesome",
      "title": "The Turing Machine"
	}
  }
}

If a user adds a new post it would be stored as /posts/2. This would work if only a single author were adding posts, but in our collaborative blogging application many users may add posts at the same time. If two authors write to /posts/2 simultaneously, then one of the posts would be deleted by the other.

To solve this, Firebase provides a push() function that generates a unique ID every time a new child is added to the specified Firebase reference. By using unique child names for each new element in the list, several clients can add children to the same location at the same time without worrying about write conflicts. The unique ID generated by push() is based on a timestamp, so list items will automatically be ordered chronologically.

We can add posts to our blogging app with chronological, unique IDs by doing the following:

Firebase postRef = ref.child("posts");

Map<String, String> post1 = new HashMap<String, String>();
post1.put("author", "gracehop");
post1.put("title", "Announcing COBOL, a New Programming Language");
postRef.push().setValue(post1);

Map<String, String> post2 = new HashMap<String, String>();
post2.put("author", "alanisawesome");
post2.put("title", "The Turing Machine");
postRef.push().setValue(post2);

Because we used push(), Firebase generated a timestamp-based, unique ID for each blog post, and no write conflicts will occur if multiple users create a blog post at the same time. Our data in the Firebase database now looks like this:

{
  "posts": {
    "-JRHTHaIs-jNPLXOQivY": {
      "author": "gracehop",
      "title": "Announcing COBOL, a New Programming Language"
    },
    "-JRHTHaKuITFIhnj02kE": {
      "author": "alanisawesome",
      "title": "The Turing Machine"
    }
  }
}

Getting the Unique ID Generated by push()

Calling push() will return a reference to the new data path, which you can use to get the value of its ID or set data to it. The following code will result in the same data as the above example, but now we'll have access to the unique push ID that was generated:

// Generate a reference to a new location and add some data using push()
Firebase postRef = ref.child("posts");
Firebase newPostRef = postRef.push();

// Add some data to the new location
Map<String, String> post1 = new HashMap<String, String>();
post1.put("author", "gracehop");
post1.put("title", "Announcing COBOL, a New Programming Language");
newPostRef.setValue(post1);

// Get the unique ID generated by push()
String postId = newPostRef.getKey();

As you can see, calling getKey() on our push() reference gives us the value of the unique ID.

Interested in learning more about how push IDs are generated? Check out this blog post.

In the next section on Retrieving Data, we'll learn how to read data from our database.

Saving Transactional Data

When working with complex data that could be corrupted by concurrent modifications, such as incremental counters, we provide a transaction operation. You give this operation two arguments: an update function and an optional completion callback. The update function takes the current state of the data as an argument and will return the new desired state you would like to write. For example, if we wanted to increment the number of upvotes on a specific blog post, we would write a transaction like the following:

Firebase upvotesRef = new Firebase("https://docs-examples.firebaseio.com/android/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes");

upvotesRef.runTransaction(new Transaction.Handler() {
    @Override
    public Transaction.Result doTransaction(MutableData currentData) {
        if(currentData.getValue() == null) {
            currentData.setValue(1);
        } else {
            currentData.setValue((Long) currentData.getValue() + 1);
        }

        return Transaction.success(currentData); //we can also abort by calling Transaction.abort()
    }

    @Override
    public void onComplete(FirebaseError firebaseError, boolean committed, DataSnapshot currentData) {
        //This method will be called once with the results of the transaction.
    }
});

We use currentData.getValue() != null to see if the counter is null or hasn't been incremented yet, since transactions can be called with null if no default value was written.

If the above code had been run without a transaction function and two clients attempted to increment it simultaneously, they would both write 1 as the new value, resulting in one increment instead of two.

Transaction Function is Called Multiple Times

doTransaction() will be called multiple times and must be able to handle null data. Even if there is existing data in your remote database, it may not be locally cached when the transaction function is run.

More details on transactions can be found in our API docs.

Network Connectivity and Offline Writes

If a client loses network connection, your app will continue functioning correctly.

Every client connected to a Firebase database maintains its own internal version of any active data. When data is written, it is written to this local version first. The Firebase client then synchronizes that data with the remote database servers and with other clients on a 'best-effort' basis.

As a result, all writes to the database will trigger local events immediately, before any data has even been written to the server. This means our app will remain responsive regardless of network latency or Internet connectivity.

Once connectivity is reestablished, we'll receive the appropriate set of events so that the client "catches up" with the current server state, without having to write any custom code.

Non-Android Runtimes

The Firebase Java client does not require the Android Runtime and can be used in any Java Runtime, including server-side Java processes or Java desktop applications.

The Firebase Java client uses daemon threads, meaning it will not prevent a process from exiting. When running on a non-Android runtime we may need to take extra steps to make sure that data is written to the server before our process exits. We can attach a CompletionListener to write operations and use a Semaphore or CountDownLatch object to prevent the process from exiting, as shown in the example below.

final CountDownLatch done = new CountDownLatch(1);
ref.setValue("SOME DATA", new Firebase.CompletionListener() {
    @Override
    public void onComplete(FirebaseError firebaseError, Firebase firebase) {
        System.out.println("done");
        done.countDown();
    }
});
done.await();

Securing Your Data

Firebase has a security language that lets you define which users have read and write access to different nodes of your data. You can read more about it in Securing Your App.

  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