I am using a transaction to update a float value in my Firebase database (see below). It works fine, except for this one time when the value didn't update. Everything else worked perfectly (you will see that there is also an object update in the onComplete). On a side note; this update to a different child in the database showed that Input was the correct value.
I have not been able to recreate this, nor can I find anything in the logs related to this particular transaction.
My question is, does this implementation of a Firebase transaction look error prone. IOW, have I implemented it incorrectly?
private void onInboundTransactionConfirm(DatabaseReference piclCount) {
piclCount.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(#NonNull MutableData mutableData) {
float P = mutableData.getValue(Float.class);
// Set value and report transaction success
mutableData.setValue(P + Input);
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b,
DataSnapshot dataSnapshot) {
String transKey = transactionRef.child(recipientId).push().getKey();
TransactionRecord inbound = new TransactionRecord("Transfer", Input, userDirectCode, UserName);
transactionRef.child(recipientId).child(transKey).setValue(inbound);
// TransactionRecord completed
Log.d(TAG, "postInboundTransaction:onComplete:" + databaseError);
}
});
}
EDIT 1:
The following warning is attached to this line: float P = mutableData.getValue(Float.class);:
Unboxing of 'mutableData.getValue(Float.class)' may produce 'java.lang.NullPointerException'
EDIT 2:
I could do this:
public Transaction.Result doTransaction(#NonNull MutableData mutableData) {
float P = mutableData.getValue(Float.class);
if (Input != 0) {
// Set value and report transaction success
mutableData.setValue(P + Input);
}
return Transaction.success(mutableData);
}
But my concern is that if the updated value does not get set (ie for some reason Input is equal to 0), would the transaction not then see the return of the non-updated value to the database as being what the user wants it to be set to, therefore not changing the value and ending successfully?
Related
as the title. The OnCompleteListener will fire even when the child I remove does not exist. How can I prevent OnComplete (or OnSuccess ) fire when remove a not exist child ?
this is the code:
ROOT_REF.child("111").removeValue().addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
Log.d("SSSSSSSSSSSSSSSSSSSS","onComplete");
}
});
note : the child "111" does not exist.
An onComplete listener fires when the state you set has been accomplished on the server. There is no way to change this behavior of the onComplete listener.
If you want to ensure that only a single user can remove a value, and that you know it is the current user, you should use a transaction. With a transaction you determine the new value of a node, based on its current value.
ROOT_REF.child("111").runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
Object value = mutableData.getValue();
if (value == null) {
// Node doesn't exist
return Transaction.success(mutableData);
}
// Remove value and report transaction success
mutableData.setValue(null);
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b,
DataSnapshot dataSnapshot) {
// Transaction completed
Log.d(TAG, "postTransaction:onComplete:" + databaseError);
}
});
Note that:
A transaction's doTransaction method may fire multiple time, precisely when another user has modified the value already. For more on this, see Firebase runTransaction not working - MutableData is null.
Since transactions read the current value of the node, they only work when the user is online.
I am getting a strange update in some but not all of my data fields with a firebase transaction.
I have a tinder style app where I only look at "posts" that have a date in the future. I can see in my db that my test post has a date in the future and opens in the app. If the date is in the past it does not open as expected. My problem is that after I perform a swipe, triggering the code below, the date field seems to be updated to a semi-random date which is not the date on my computer or the device but is always the 28th of some month. As far as I can tell the code below should not be updating the date but even the log statement shows the date to have been updated
public void swipeReject(String postId) {
getPostRef(postId);
onSwiped(globalPostRef, FALSE);
}
private void getPostRef(String postId){
mFirebaseDatabaseReference = FirebaseDatabase.getInstance().getReference();
globalPostRef = mFirebaseDatabaseReference.child("messages").child(postId);
}
private void onSwiped(DatabaseReference postRef, Boolean accepted) {
final Boolean accept = accepted;
postRef.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
PostMessage p = mutableData.getValue(PostMessage.class);
if (p == null) {
return Transaction.success(mutableData);
}
Log.e("date", String.valueOf(p.getDate()));
if (p.votes.containsKey(mFirebaseUser.getUid())) {
// This shouldn't happen
} else {
if (accept){
p.votes.put(mFirebaseUser.getUid(), "upvote");
p.upVoteCount = p.upVoteCount + 1;
}else{
p.votes.put(mFirebaseUser.getUid(), "downvote");
p.downVoteCount = p.downVoteCount + 1;
}
}
// 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
if (databaseError != null){
Log.e(TAG, "postTransaction:onComplete:" + databaseError);
}
}
});
code prior to this simply reads the data from the db with no write operations. I have no idea this field is updating?
Anything I can do to debug this?
Turns out there was nothing wrong with my code. The error was in my db values. While the Milliseconds since Epoch was correct there was also another date field that recorded the year that was not correct. Once this was updated everything made sense.
ATNRef= FirebaseDatabase.getInstance().getReference("AvailableTokenNumber");
ATNRef.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
if (mutableData.getValue(int.class ) == 0){
mutableData.setValue(2);
data.tokenNo = 1;
return Transaction.success(mutableData);
}else {
data.tokenNo=mutableData.getValue(int.class);
mutableData.setValue(data.tokenNo + 1);
return Transaction.success(mutableData);
}
}
Whenever the code is run for first time, value of data.tokenNo is 0.
After that it is updating correctly as per the database value.
I am not able to figure it out what makes the value of data.token = 0 on every first run of the app/code.
You should be expecting that the initial snapshot of data available in your transaction could be null during its first execution. Note this from the documentation:
Note: Because doTransaction() is called multiple times, it must be
able to handle null data. Even if there is existing data in your
remote database, it may not be locally cached when the transaction
function is run, resulting in null for the initial value.
Also in the javadoc it says:
This method will be called, possibly multiple times, with the current
data at this location.
Expect that the first time your handler is run, it will be dealing with an empty database. Then, when the data there is known (if any), you should be prepared to handle that case also. If you don't want to do anything where there is no data (or unknown data) at the location, simply return a successful Transaction with no changes to mutableData.
Thank you for explaining, #Doug Stevenson.
I understand this situation from your answer.
Then I solved this problem with this.
This works for me at least.
FirebaseDatabase database = FirebaseDatabase.getInstance();
try {
database.setPersistenceEnabled(true);
} catch (Exception e) {
// ignore
}
...
DatabaseReference ref = database.getReference("somewhere");
// Read value from database to synchronize localDB with remoteDB
ref.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
// Then execute transaction.
ref.runTransaction(new Transaction.Handler() {
#NonNull
#Override
public Transaction.Result doTransaction(#NonNull MutableData mutableData) {
...
return Transaction.success(mutableData);
}
#Override
public void onComplete(#Nullable DatabaseError databaseError, boolean committed, #Nullable DataSnapshot dataSnapshot) {
...
}
});
}
});
I know this is a common error and there are many threads in SO. But nothing helped me.
Here is my problem:
My DB structure:
root
/posts/
/user-posts/userId/
Each post has few attributes including:
likeCount
likes > id : true
Whenever a post is created, it is stored in both /posts/ and /user-posts/ node. But when I likes the post, it updates in one and fails in another with the error mentioned in Question title.
Code used for inserting a post in firebase DB:
Map<String, Object> childUpdates = new HashMap<>();
childUpdates.put("/posts/" + key, feedValues);
childUpdates.put("/user-posts/" + userId + "/" + key, feedValues);
dbRef.updateChildren(childUpdates);
Code used for like:
public static void updateLikeInServer(DatabaseReference dbRef, final String userId) {
Log.d(TAG, "updateLikeInServer:started:");
dbRef.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
Post post = mutableData.getValue(Post.class);
if (post == null) { // It comes as null for /user-posts/ but not for /posts/
return Transaction.success(mutableData);
}
if (post.getLikes().containsKey(userId)) {
post.setLikesCount(post.getLikesCount() - 1);
post.getLikes().remove(userId);
} else {
post.setLikesCount(post.getLikesCount() + 1);
post.getLikes().put(userId, true);
}
// Set value and report transaction success
mutableData.setValue(post);
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b,
DataSnapshot dataSnapshot) {
// Transaction completed
Log.d(TAG, "updateLikeInServer:onComplete: " + databaseError);
}
});
}
Since the like count need to be updated in both /user-posts/ and /posts/, I am calling the above method twice with corresponding DatabaseReference.
Arguments:
If it fails, it should fail for both the nodes /user-posts/ and /posts/. Why it's not failing for both locations?
It only fails only for 1st time like, but if you do it consecutively like > dislike > like > dislike, it succeeds for both locations from 2nd time on wards. Why so?
MOST IMPORTANT: The error comes only when I like the post created by friend. If I like my own post, no error.
Got rid of the problem with below workaround. Don't know whether this is the proper way or not, but would love to know from others if any better solution is there.
Since it only happens for 1st time transaction, I am trying to check the error and retry the same operation again which succeeds in the 2nd attempt.
#Override
public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) {
// Transaction completed
Log.d(TAG, "updateLikeInServer:onComplete: " + databaseError);
if (databaseError != null && databaseError.getCode() == DatabaseError.OVERRIDDEN_BY_SET) {
// Retry the same
updateLikeInServer(dbRef, userId);
}
}
The above snippet fixed my problem. But I would love to listen best answer if possible.
-uniqueid1
status:0
userTokenId: uniqueid(which is null)
I am having a problem of race around condition where I don't know how firebase is implementing this case. The problem is as follows:
user checks status for whether the status is 0.
if status is 0 it adds its token to userTokenid field and status 1
if not then it does'nt add its tokenid.
Now the problem is when 2 or more user check status 0 and both enter the 2 point. This question might be pointless but I need to confirm. Do i have to worry about this case?
firebase.child('uniqueid1');
firebase.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if(datasnapshot.child('status').getValue().equals(0)){
//update record for userTokenId and status
}
else{
//don't update
}
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
If both clients happen to update the same value around the same time with a setValue() or updateChildren() call, the last write wins.
If you don't want that, you should probably be using a transaction. From that documentation:
When working with complex data that could be corrupted by concurrent modifications, such as incremental counters, we provide a transaction operation. You give this operation two arguments: an update function and an optional completion callback. The update function takes the current state of the data as an argument and will return the new desired state you would like to write. For example, if we wanted to increment the number of upvotes on a specific blog post, we would write a transaction like the following:
Firebase upvotesRef = new Firebase("https://docs-examples.firebaseio.com/android/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes");
upvotesRef.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData currentData) {
if(currentData.getValue() == null) {
currentData.setValue(1);
} else {
currentData.setValue((Long) currentData.getValue() + 1);
}
return Transaction.success(currentData); //we can also abort by calling Transaction.abort()
}
#Override
public void onComplete(FirebaseError firebaseError, boolean committed, DataSnapshot currentData) {
//This method will be called once with the results of the transaction.
}
});
Read the documentation on transaction() for full details.
Firebase ref=firebase.child("order_details/"+orderId);
ref.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData currentData) {
Log.d("status",currentData.getValue());
int status= (int) currentData.child("status").getValue();
if(status==0){
HashMap<String,Object> map=new HashMap<String, Object>();
map.put("TokenId",sharedPreferences.getString("tokenId",""));
map.put("status",1);
currentData.setValue(map);
}
((MapLoadingActivity)getActivity()).setProgressVisibility(false);
return Transaction.success(currentData);
}
#Override
public void onComplete(FirebaseError firebaseError, boolean b, DataSnapshot dataSnapshot) {
}
});
I know its late to ask the question,but doTransaction() is pretty weird sometimes it calls sometimes it simply returns null.Therefore i switched my approach to php by calling firebase using php wrapper class.Please do suggest ,if my approach is wrong.