Reputation: 31489
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?
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.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
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