I'm working on an app that does something similar to the star count on a blog post example given in the Firebase documentation and I have a question about the design of the polling system and whether to use .
From the example, the data for a post looks like this:
postid {
// some data here about the post, author, id, etc,
starCount : <an integer count of the stars>,
stars : {
<user who starred> : true,
<user who starred> : true,
// ... and so on, basically a list of all the users who starred the post
}
}
The docs use a transaction to update the star count:
private void onStarClicked(DatabaseReference postRef) {
postRef.runTransaction(new Transaction.Handler() {
@Override
public Transaction.Result doTransaction(MutableData mutableData) {
Post p = mutableData.getValue(Post.class);
if (p == null) {
return Transaction.success(mutableData);
}
if (p.stars.containsKey(getUid())) {
// Unstar the post and remove self from stars
p.starCount = p.starCount - 1;
p.stars.remove(getUid());
} else {
// Star the post and add self to stars
p.starCount = p.starCount + 1;
p.stars.put(getUid(), true);
}
// Set value and report transaction success
mutableData.setValue(p);
return Transaction.success(mutableData);
}
@Override
public void onComplete(DatabaseError databaseError, boolean b,
DataSnapshot dataSnapshot) {
// Transaction completed
Log.d(TAG, "postTransaction:onComplete:" + databaseError);
}
});
}
Because of the use of a transaction both the starCount
and stars
seem to need to be part of the same object. Why? Because I want to use both a transaction and assure automicness. The other option for assuring automicness is multi-path updates, but as noted here in the comments you cannot use multi-path updates with transitions. You have access to the mutableData
(in this case the post object) and that's it.
SO if the starCount
and stars
must be stored in the same location, I don't understand the point of the starCount. Whenever you get a post object, it will include stars
, the list of everyone who starred it. Why not remove the starCount entirely and just get the size of the stars list? Is there something non performant about this?
In this case, starring/unstarring something would be something like p.stars.put(getUid(), true);
and p.stars.remove(getUid());
. If I did that, I don't see the need for a transaction, because I no longer have multiple users updating a "counter" object. I could use a transaction for the p.stars.put(getUid(), true);
statement, but it seems highly unlikely to me that the same user who try to star/unstar something from two different devices, and even if they did, it wouldn't be disastrous as far as I can tell.
So what am I missing here? Why use transactions here and what benefit does storing and manually tracking starCount add? I could see a performance benefit storing the stars
user list elsewhere in the database, as shown in this example, but then the transaction wouldn't work.
Any insight would be appreciated and thank you!
Transactions are useful when you want to update a field's value based on its current value, or the value of some other field. A transaction consists of any number of get() operations followed by any number of write operations such as set() , update() , or delete() .
All Firebase Realtime Database data is stored as JSON objects. You can think of the database as a cloud-hosted JSON tree. Unlike a SQL database, there are no tables or records. When you add data to the JSON tree, it becomes a node in the existing JSON structure with an associated key.
In our effort to create a single working sample app from which to get the documentation samples, we indeed may have created a non-sensible (but functional) use for transactions.
I'd recommend having a look at my answer to this question for a more reasonable (but definitely complex) way of doing secure counts using multi-location updates and server-side security rules.
In general though, I'd always recommend splitting the voting logic from the counting logic in cases like this. So when a user votes for a post, you write a simple record into the database that records their action:
votesQueue
<push id>
<uid>: true
And then you have a tiny server script (e.g. a firebase queue worker) that listens to this queue and aggregates the number of votes.
This approach is very common when using Firebase and makes matters a lot simpler. You can probably easily imagine how you'd secure these two nodes (votesQueue
and voteTotals
), while I can assure you that the solution in the answer I linked above gives me quite some maintenance headaches.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With