In this document we'll cover the four methods for writing data to your Firebase Realtime Database: set, update, push, and our transaction feature.
Ways to Save Data
set | Write or replace data to a defined path, like messages/users/<username> |
update | Update some of the keys for a defined path without replacing all of the data |
push | Add to a list of data in the database. Every time you push a new node onto a list, your database generates a unique key, like messages/users/<unique-user-id>/<username> |
transaction | Use our transactions feature when working with complex data that could be corrupted by concurrent updates |
Saving Data
The basic database write operation is a set which saves new data to the specified database reference, replacing any existing data at that path. To understand set, we'll build a simple blogging app. The data for our app will be stored at this database reference:
Java
final FirebaseDatabase database = FirebaseDatabase.getInstance(); DatabaseReference ref = database.getReference("server/saving-data/fireblog");
Node.js
var db = firebase.database(); var ref = db.ref("server/saving-data/fireblog");
Let's start by saving some user data. We'll store each user by a unique username, and we'll also store their full name and date of birth. Since each user will have a unique username, it makes sense to use the set method here instead of the push method since we already have the key and don't need to create one.
First, we'll create a database reference to our user data. Then we'll use set()
/ setValue()
to save a user object to the database with the user's username, full name, and birthday. We can pass set a string, number, boolean, null
, array or any JSON object. Passing null
will remove the data at the specified location. In this case we'll pass it an object:
Java
public static class User { public String date_of_birth; public String full_name; public String nickname; public User(String date_of_birth, String full_name) { // ... } public User(String date_of_birth, String full_name, String nickname) { // ... } } DatabaseReference usersRef = ref.child("users"); Map<String, User> users = new HashMap<String, User>(); users.put("alanisawesome", new User("June 23, 1912", "Alan Turing")); users.put("gracehop", new User("December 9, 1906", "Grace Hopper")); usersRef.setValue(users);
Node.js
var usersRef = ref.child("users"); usersRef.set({ alanisawesome: { date_of_birth: "June 23, 1912", full_name: "Alan Turing" }, gracehop: { date_of_birth: "December 9, 1906", full_name: "Grace Hopper" } });
When a JSON object is saved to the database, the object properties are automatically mapped to database child locations in a nested fashion. Now if we navigate to the URL https://docs-examples.firebaseio.com/server/saving-data/fireblog/users/alanisawesome/full_name, we'll see the value "Alan Turing". You can also save data directly to a child location:
Java
usersRef.child("alanisawesome").setValue(new User("June 23, 1912", "Alan Turing")); usersRef.child("gracehop").setValue(new User("December 9, 1906", "Grace Hopper"));
Node.js
usersRef.child("alanisawesome").set({ date_of_birth: "June 23, 1912", full_name: "Alan Turing" }); usersRef.child("gracehop").set({ date_of_birth: "December 9, 1906", full_name: "Grace Hopper" });
The above two examples - writing both values at the same time as an object and writing them separately to child locations - will result in the same data being saved to your database:
{ "users": { "alanisawesome": { "date_of_birth": "June 23, 1912", "full_name": "Alan Turing" }, "gracehop": { "date_of_birth": "December 9, 1906", "full_name": "Grace Hopper" } } }
The first example will only trigger one event on clients that are watching the data, whereas the second example
will trigger two. It is important to note that if data already existed at usersRef
, the first approach
would overwrite it, but the second method would only modify the value of each separate child node while leaving
other children of usersRef
unchanged.
Updating Saved Data
If you want to write to multiple children of a database location at the same time without overwriting other child nodes, you can use the update method as shown below:
Java
DatabaseReference hopperRef = usersRef.child("gracehop"); Map<String, Object> hopperUpdates = new HashMap<String, Object>(); hopperUpdates.put("nickname", "Amazing Grace"); hopperRef.updateChildren(hopperUpdates);
Node.js
var hopperRef = usersRef.child("gracehop"); hopperRef.update({ "nickname": "Amazing Grace" });
This will update Grace's data to include her nickname. If we had used set here instead of update,
it would have deleted both full_name
and date_of_birth
from our hopperRef
.
The Firebase Realtime Database also supports multi-path updates. This means that update can now update values at multiple locations in your 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 Grace and Alan at the same time:
Java
Map<String, Object> userUpdates = new HashMap<String, Object>(); userUpdates.put("alanisawesome/nickname", "Alan The Machine"); userUpdates.put("gracehop/nickname", "Amazing Grace"); usersRef.updateChildren(userUpdates);
Node.js
usersRef.update({ "alanisawesome/nickname": "Alan The Machine", "gracehop/nickname": "Amazing Grace" });
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:
Java
Map<String, Object> userNicknameUpdates = new HashMap<String, Object>(); userNicknameUpdates.put("alanisawesome", new User(null, null, "Alan The Machine")); userNicknameUpdates.put("gracehop", new User(null, null, "Amazing Grace")); usersRef.updateChildren(userNicknameUpdates);
Node.js
usersRef.update({ "alanisawesome": { "nickname": "Alan The Machine" }, "gracehop": { "nickname": "Amazing Grace" } });
This results in different behavior, namely overwriting the entire /users
node:
{ "users": { "alanisawesome": { "nickname": "Alan The Machine" }, "gracehop": { "nickname": "Amazing Grace" } } }
Adding a Completion Callback
If you'd like to know when your data has been committed, you can add a completion callback. Both set and update take an optional completion callback that is called when the write has been committed to the database. If the call was unsuccessful for some reason, the callback will be passed an error object indicating why the failure occurred.
Java
DatabaseReference dataRef = ref.child("data"); dataRef.setValue("I'm writing data", new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) { if (databaseError != null) { System.out.println("Data could not be saved " + databaseError.getMessage()); } else { System.out.println("Data saved successfully."); } } });
Node.js
dataRef.set("I'm writing data", function(error) { if (error) { alert("Data could not be saved." + error); } else { alert("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 set to store children with auto-incrementing integer indexes, like the following:
// NOT RECOMMENDED - use push() instead! { "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, the Firebase clients provide a push()
function that generates a
unique key for each new child. By using unique child keys, several clients can
add children to the same location at the same time without worrying about write conflicts.
Java
public static class Post { public String author; public String title; public Post(String author, String title) { // ... } } DatabaseReference postsRef = ref.child("posts"); DatabaseReference newPostRef = postsRef.push(); newPostRef.setValue(new Post("gracehop", "Announcing COBOL, a New Programming Language")); // We can also chain the two calls together postsRef.push().setValue(new Post("alanisawesome", "The Turing Machine"));
Node.js
var postsRef = ref.child("posts"); var newPostRef = postsRef.push(); newPostRef.set({ author: "gracehop", title: "Announcing COBOL, a New Programming Language" }); // we can also chain the two calls together postsRef.push().set({ author: "alanisawesome", title: "The Turing Machine" });
The unique key is based on a timestamp, so list items will automatically be ordered chronologically. Because we generate a unique key for each blog post, no write conflicts will occur if multiple users add a post at the same time. Our database data now looks like this:
{ "posts": { "-JRHTHaIs-jNPLXOQivY": { "author": "gracehop", "title": "Announcing COBOL, a New Programming Language" }, "-JRHTHaKuITFIhnj02kE": { "author": "alanisawesome", "title": "The Turing Machine" } } }
In JavaScript, the pattern of calling push()
and then immediately calling set()
is
so common that we let you combine them by passing the data to be set directly to push()
as follows:
Java
// No Java equivalent
Node.js
// This is equivalent to the calls to push().set(...) above postsRef.push({ author: "gracehop", title: "Announcing COBOL, a New Programming Language" });
Getting the unique key generated by push()
Calling push()
will return a reference to the new data path, which you can use to get the key 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 key that was generated:
Java
// Generate a reference to a new location and add some data using push() DatabaseReference pushedPostRef = postsRef.push(); // Get the unique ID generated by a push() String postId = pushedPostRef.getKey();
Node.js
// Generate a reference to a new location and add some data using push() var newPostRef = postsRef.push(); // Get the unique key generated by push() var postId = newPostRef.key;
As you can see, we can get the value of the unique key from our push()
reference.
In the next section on Retrieving Data, we'll learn how to read this data from a Firebase 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 callbacks: 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:
Java
DatabaseReference upvotesRef = ref.child("server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes"); upvotesRef.runTransaction(new Transaction.Handler() { @Override public Transaction.Result doTransaction(MutableData mutableData) { Integer currentValue = mutableData.getValue(Integer.class); if (currentValue == null) { mutableData.setValue(1); } else { mutableData.setValue(currentValue + 1); } return Transaction.success(mutableData); } @Override public void onComplete(DatabaseError databaseError, boolean committed, DataSnapshot dataSnapshot) { System.out.println("Transaction completed"); } });
Node.js
var upvotesRef = db.ref("server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes"); upvotesRef.transaction(function (current_value) { return (current_value || 0) + 1; });
We check 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.
Network Connectivity and Offline Writes
Every Firebase client maintains its own internal version of any active data. When data is written, it is written to this local version first. The client then synchronizes that data with the database 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 database. This means that when we write an application using Firebase, 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.
Securing Your Data
The Firebase Realtime Database 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 Secure Your Data.