Skip to content

Andrew Birck's Blog

Firebase Realtime Database Returning Stale Results

May 18, 2021

Continuing my series of posts about issues I hit as a first time mobile game developer today I’m writing about an issue I found surprisingly little about on the internet. Maybe it’ll mostly be a moot point in the near future since Google is pushing Cloud Firestore over Realtime Database but I spent long enough banging my head against the wall that I hope this helps someone else.

The problem

I’m developing a game in Unity and decided to use Google Firebase to handle things like authentication, storing data, etc… When I went to go build out my database to store users and game state I found there are two options:

  1. Firebase Realtime Database
  2. Cloud Firestore

I can’t really remember why I picked the Realtime Database (maybe it was the preferred option when I started?) but I did.

I created the database, populated it with information and then wrote some code that looked like:

class DatabaseManager {
public async Task<DbModels.v1.User> GetActiveUsers(FirebaseUser fbUser)
{
...
return await FirebaseDatabase.DefaultInstance
.GetReference($"users/{fbUser.UserId}/activeGames")
.GetValueAsync().ContinueWith(task => {
if (task.IsCompleted)
{
DataSnapshot snapshot = task.Result;
var json = snapshot.GetRawJsonValue();
return (List<Game>)JsonUtility.FromJson(json, typeof(List<Game>));
}
...
});
}
}

The issue

The problem was sometimes I would get back an empty list even though I could log in to the Firebase console and SEE there was data there. I did a lot of Google searching to try to find others with the same issue to no avail until FINALLY I found a post somewhere mentioning that the Realtime Database API might initially return a cached response when querying a value. The API of course contacts the server afterwards to get updated values but they’re never sent along if you’re just using GetValueAsync(). What made it tough to diagnose was this wasn’t consistent behavior.

Getting a cached response was of course what was happening when I was seeing data that didn’t correspond with what I knew was on the server. I assumed GetValueAsync() would always query the server for the latest value but the Realtime Database API really expects you go keep a listener on a value and respond to updates.

One solution

Obviously the quickest solution was to stop assuming I’d be receiving the lastest value when I query the database and instead setup a listener that isn’t immediately torn down.

Since I was going to have to set up a listener to get all data updates anyway I decided to set it up on the user (’users/<id>’) rather than ’users/<id>/activeGames’ so I could get other updates as well.

But when should we start listening to a user’s data? It made sense for me to do so right after the user login. So first I added a user login event to my AuthManager class and then subscribed to the event while constructing my DatabaseManager:

class DatabaseManager
{
...
public DatabaseManager(AuthManager authManager)
{
authManager.UserLoggedIn += UserLoggedInHandler;
}
private void UserLoggedInHandler(object sender, UserLoggedInEventArgs e)
{
FirebaseDatabase.DefaultInstance
.GetReference($"users/{e.LoggedInUser.UserId}")
.ValueChanged += UserValueChangedHandler;
}
}

And then expose an ActiveGamesUpdated event:

public class ActiveGamesEventArgs
{
public ActiveGamesEventArgs(List<Game> games) { Games = games; }
public List<Game> Games { get; }
}
public class DatabaseManager
{
public System.Action<object, ActiveGamesEventArgs> ActiveGamesUpdated { get; internal set; }
public void AttachActiveGameEventListener(Action<object, ActiveGamesEventArgs> handler) {
ActiveGamesUpdated += handler;
// send existing state
if (_latestActiveGameEventArgs != null)
{
handler(this, _latestActiveGameEventArgs);
}
}
private void UserValueChangedHandler(object sender, ValueChangedEventArgs args)
{
if (args.DatabaseError != null)
{
Debug.LogError(args.DatabaseError.Message);
return;
}
string json = args.Snapshot.GetRawJsonValue();
User user = (User)JsonUtility.FromJson(json, typeof(User));
_latestActiveGameEventArgs = new ActiveGamesEventArgs(user.activeGames);
this.ActiveGamesUpdated.Invoke(this, _latestActiveGameEventArgs);
}
}

The solution ended up being quite a bit more boilerplate than just calling GetValueAsync() but at least I get the correct value.

I’m currently looking at whether Firebase Cloud acts in the same way or not hoping that I can move away from the an event driven architecture the Realtime Database forced on me because it just doesn’t fit what I need for my app.


Andrew Birck
A place to for me to post stuff (mostly)
about software development.