Save Data

Before you begin

Before you can use Realtime Database, you need to:

  • Register your Unity project and configure it to use Firebase.

    • If your Unity project already uses Firebase, then it's already registered and configured for Firebase.

    • If you don't have a Unity project, you can download a sample app.

  • Add the Firebase Unity SDK (specifically, FirebaseDatabase.unitypackage) to your Unity project.

Note that adding Firebase to your Unity project involves tasks both in the Firebase console and in your open Unity project (for example, you download Firebase config files from the console, then move them into your Unity project).

Saving Data

There are five methods for writing data to the Firebase Realtime Database:

Method Common uses
SetValueAsync() Write or replace data to a defined path, such as users/<user-id>/<username>.
SetRawJsonValueAsync() Write or replace data with raw Json, such as users/<user-id>/<username>.
Push() Add to a list of data. Every time you call Push(), Firebase generates a unique key that can also be used as a unique identifier, such as user-scores/<user-id>/<unique-score-id>.
UpdateChildrenAsync() Update some of the keys for a defined path without replacing all of the data.
RunTransaction() Update complex data that could be corrupted by concurrent updates.

Get a DatabaseReference

To write data to the Database, you need an instance of DatabaseReference:

using Firebase;
using Firebase.Database;

public class MyScript: MonoBehaviour {
  void Start() {
    // Get the root reference location of the database.
    DatabaseReference reference = FirebaseDatabase.DefaultInstance.RootReference;
  }
}

Write, update, or delete data at a reference

Basic write operations

For basic write operations, you can use SetValueAsync() to save data to a specified reference, replacing any existing data at that path. You can use this method to pass types that correspond to the available JSON types as follows:

  • string
  • long
  • double
  • bool
  • Dictionary<string, Object>
  • List<Object>

If you use a typed C# object, you can use the built in JsonUtility.ToJson() to convert the object to raw Json and call SetRawJsonValueAsync(). For example, you may have a User class that looked as follows:

public class User {
    public string username;
    public string email;

    public User() {
    }

    public User(string username, string email) {
        this.username = username;
        this.email = email;
    }
}

You can add a user with SetRawJsonValueAsync() as follows:

private void writeNewUser(string userId, string name, string email) {
    User user = new User(name, email);
    string json = JsonUtility.ToJson(user);

    mDatabaseRef.Child("users").Child(userId).SetRawJsonValueAsync(json);
}

Using SetValueAsync() or SetRawJsonValueAsync() in this way overwrites data at the specified location, including any child nodes. However, you can still update a child without rewriting the entire object. If you want to allow users to update their profiles you could update the username as follows:

mDatabaseRef.Child("users").Child(userId).Child("username").SetValueAsync(name);

Append to a list of data

Use the Push() method to append data to a list in multiuser applications. The Push() method generates a unique key every time a new child is added to the specified Firebase reference. By using these auto-generated keys for each new element in the list, several clients can add children to the same location at the same time without write conflicts. The unique key generated by Push() is based on a timestamp, so list items are automatically ordered chronologically.

You can use the reference to the new data returned by the Push() method to get the value of the child's auto-generated key or set data for the child. Calling Key on a Push() reference returns the value of the auto-generated key.

Update specific fields

To simultaneously write to specific children of a node without overwriting other child nodes, use the UpdateChildrenAsync() method.

When calling UpdateChildrenAsync(), you can update lower-level child values by specifying a path for the key. If data is stored in multiple locations to scale better, you can update all instances of that data using data fan-out. For example, a game might have a LeaderboardEntry class like this:

public class LeaderboardEntry {
    public string uid;
    public int score = 0;

    public LeaderboardEntry() {
    }

    public LeaderboardEntry(string uid, int score) {
        this.uid = uid;
        this.score = score;
    }

    public Dictionary<string, Object> ToDictionary() {
        Dictionary<string, Object> result = new Dictionary<string, Object>();
        result["uid"] = uid;
        result["score"] = score;

        return result;
    }
}

To create a LeaderboardEntry and simultaneously update it to the recent score feed and the user's own score list, the game uses code like this:

private void WriteNewScore(string userId, int score) {
    // Create new entry at /user-scores/$userid/$scoreid and at
    // /leaderboard/$scoreid simultaneously
    string key = mDatabase.Child("scores").Push().Key;
    LeaderBoardEntry entry = new LeaderBoardEntry(userId, score);
    Dictionary<string, Object> entryValues = entry.ToDictionary();

    Dictionary<string, Object> childUpdates = new Dictionary<string, Object>();
    childUpdates["/scores/" + key] = entryValues;
    childUpdates["/user-scores/" + userId + "/" + key] = entryValues;

    mDatabase.UpdateChildrenAsync(childUpdates);
}

This example uses Push() to create an entry in the node containing entries for all users at /scores/$key and simultaneously retrieve the key with Key. The key can then be used to create a second entry in the user's scores at /user-scores/$userid/$key.

Using these paths, you can perform simultaneous updates to multiple locations in the JSON tree with a single call to UpdateChildrenAsync(), such as how this example creates the new entry in both locations. Simultaneous updates made this way are atomic: either all updates succeed or all updates fail.

Delete data

The simplest way to delete data is to call RemoveValue() on a reference to the location of that data.

You can also delete by specifying null as the value for another write operation such as SetValueAsync() or UpdateChildrenAsync(). You can use this technique with UpdateChildrenAsync() to delete multiple children in a single API call.

Know when your data is committed.

To know when your data is committed to the Firebase Realtime Database server, you can add a continuation. Both SetValueAsync() and UpdateChildrenAsync() return a Task that allows you to know when the operation is complete. If the call is unsuccessful for any reason, the Tasks IsFaulted will be true with the Exception property indicating why the failure occurred.

Save data as transactions

When working with data that could be corrupted by concurrent modifications, such as incremental counters, you can use a transaction operation. You give this operation a Func. This update Func takes the current state of the data as an argument and returns the new desired state you would like to write. If another client writes to the location before your new value is successfully written, your update function is called again with the new current value, and the write is retried.

For instance, in a game you could allow users to update a leaderboard with the five highest scores:

private void AddScoreToLeaders(string email, 
                               long score,
                               DatabaseReference leaderBoardRef) {

    leaderBoardRef.RunTransaction(mutableData => {
      List<object> leaders = mutableData.Value as List<object>

      if (leaders == null) {
        leaders = new List<object>();
      } else if (mutableData.ChildrenCount >= MaxScores) {
        long minScore = long.MaxValue;
        object minVal = null;
        foreach (var child in leaders) {
          if (!(child is Dictionary<string, object>)) continue;
          long childScore = (long)
                      ((Dictionary<string, object>)child)["score"];
          if (childScore < minScore) {
            minScore = childScore;
            minVal = child;
          }
        }
        if (minScore > score) {
          // The new score is lower than the existing 5 scores, abort.
          return TransactionResult.Abort();
        }

        // Remove the lowest score.
        leaders.Remove(minVal);
      }

      // Add the new high score.
      Dictionary<string, object> newScoreMap =
                       new Dictionary<string, object>();
      newScoreMap["score"] = score;
      newScoreMap["email"] = email;
      leaders.Add(newScoreMap);
      mutableData.Value = leaders;
      return TransactionResult.Success(mutableData);
    });
}

Using a transaction prevents the leaderboard from being incorrect if multiple users record scores at the same time or the client had stale data. If the transaction is rejected, the server returns the current value to the client, which runs the transaction again with the updated value. This repeats until the transaction is accepted or too many attempts have been made.

Write data offline

If a client loses its 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's 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 trigger local events immediately, before any data is written to the server. This means your app remains responsive regardless of network latency or connectivity.

Once connectivity is reestablished, your app receives the appropriate set of events so that the client syncs with the current server state, without having to write any custom code.

Next Steps