caw
caw

Reputation: 31489

Why is onChildChanged() firing before onComplete() when using transactions in Firebase?

With Firebase for Android, I'm using the following data structure for a chat room with a limited number of slots:

/rooms
  /<roomid, generated by push()>
    /users
      one: null
      two: null
      three: null

For my clients to take one of the slots (one to three), I'm using the following code as provided here (JavaScript version):

var userid = "myuserid";
var ref = new Firebase("<my-firebase>.firebaseio.com/rooms/<roomid>/users");
ref.transaction(function(users) {
  if (!users.one) {
    // Claim slot 1
    users.one = userid;
    return users;
  } else if (!users.two) {
    // Claim slot 2
    users.two = userid;
    return users;
  } else if (!users.three) {
    // Claim slot 3
    users.three = userid;
    return users;
  }
  // Room is full, abort the transaction.
  return;
}, function(err, committed, snapshot) {
  if (committed && !err) {
    // Joined room successfully.
  } else {
    // Could not join room because it was full.
  }
});

After some testing, I found that this does not work. Am I right with the following two assumptions that explain why it's not working?

  1. In runTransaction(...) (Android) or transaction(...) (JavaScript), you have to set the optional parameter fireLocalEvents (Android) or applyLocally (JavaScript) to false because otherwise you will receive events from the transaction although it might not be successful.
  2. Even with fireLocalEvents (Android) or applyLocally (JavaScript) set to false, the events for onChildChanged(...) on that reference will fire before your transaction returns successfully in onComplete(...). Is this intended behaviour?

What that means for this special case is that I will be notified about the chat room being full (in ChildEventListener.onChildChanged(...)) before I get to know that I myself am the one who got the last slot (in Transaction.Handler.onComplete(...)).

Let's assume that, if the room is full ...

If I want to do that, I have to move the complete logic for that to ChildEventListener.onChildChanged(...), because that's where I will receive the event first, right? Otherwise, the room will be hidden before I get to know that I was the one who got the last slot there.

Any ideas?

Upvotes: 1

Views: 645

Answers (1)

Kato
Kato

Reputation: 40582

This is actually one of the components that makes Firebase an amazing app and saves you tons of coding. To understand why this is in your benefit, we need to back for a second and talk about latency compensation and offline functionality.

If you've ever written an app that provides real-time features, then you've run into the spaghetti mess of connection errors and temporary outages. It leads to tons of if/else/then logic to retry things once the connection is restored.

Firebase abstracts all of this complexity so that your app works the same offline as it works when online. You can still listen for events, set/save/update data, and continue on as if online. When the connection is restored, the events are applied.

Similarly, when you do a set() operation locally, it's very slow to wait for the server to reply before applying the changes locally. Again, this leads to lots of if/else logic for latency compensation--tracking when you make changes locally and showing them immediately so the user doesn't feel like the app is unresponsive.

Firebase again handles these complexities internally. Since 99.9% of your write ops in production are going to succeed, as bugs are generally responsible for sending invalid content, we just apply the change locally by firing the correct events. Then, if the server replies with that extreme edge case, we redact the change by applying another event that corrects the status.

So in summary, Firebase data is eventually consistent when you make a change. Thus, you're going to see the local event immediately, and this is a good thing. If something goes wrong in the process, you'll see a second event to correct the data, probably a few hundred milliseconds later.

Upvotes: 1

Related Questions