Android Firebase database transaction - android

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) {
...
}
});
}
});

Related

how to prevent OnComplete fire when remove a not exist child or value

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.

Is my implementation of a Firebase Transaction correct?

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?

How do I check if specific child value exists in FireBase (Android)

I have some trouble trying to check if user information is stored already in the FireBase database.
Basically I'm trying to do something stupid like this:
"select user_name from user where user_id="+userID+"
And if the nickname exists it should make the boolean var isFirstTime = false and if it doesn't it should stay true. And after that it should show register box or not.
This is my db:
Firebase
And this is my code in onCreate method:
databaseReference = FirebaseDatabase.getInstance().getReference();
DatabaseReference dbRefFirstTimeCheck = databaseReference.child("User").child(user.getUid()).child("Nickname");
isFirstTime = true;
dbRefFirstTimeCheck.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if(dataSnapshot.getValue() != null) {
isFirstTime=false;
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
if(isFirstTime) {
showNewUserBox();
}
else {
}
No matter what I do, the methor showNewUserBox() is being called. How do I get the data i need and check if it's there?
As others have commented, data is loaded from Firebase asynchronously. By the time you check isFirstTime, the data hasn't been loaded yet, onDataChange hasn't been run yet, so ifFirstTime will have its default value (false for a boolean).
All code that requires data from the database should be inside onDataChange (or invoked from within there). The simplest fix for your code is:
databaseReference = FirebaseDatabase.getInstance().getReference();
DatabaseReference dbRefFirstTimeCheck = databaseReference.child("User").child(user.getUid()).child("Nickname");
dbRefFirstTimeCheck.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if(dataSnapshot.exists()) {
showNewUserBox();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
throw databaseError.toException(); // don't ignore errors
}
});
Also see some of the many questions about asynchronous loading from Firebase, such as getContactsFromFirebase() method return an empty list (or this quite old classic: Setting Singleton property value in Firebase Listener).

Update other nodes when the transaction is completed

I'm learning Firebase in android and I'm trying to make a room system with their respective slots. As in this case several users may want to access the same slot at the same time I decided to use Firebase transactions.
So when the user tries to log in to the slot I do:
mySlotRef1.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
Slot p = mutableData.getValue(Slot.class);
if (p == null) {
return Transaction.success(mutableData);
}
if (p.getState().equals("closed")) {
return Transaction.abort();
}
// Set value and report transaction success
Slot sl1 = new Slot("slot1", idRoom, auth.getCurrentUser().getDisplayName(), auth.getCurrentUser().getUid(), "closed", Profile.getCurrentProfile().getProfilePictureUri(200, 200).toString());
mutableData.setValue(sl1);
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b,
DataSnapshot dataSnapshot) {
// Transaction completed
Log.d(TAG, "postTransaction:onComplete:" + databaseError);
}
});
I think this works, i. e. when the slot is closed it only enters one user and rejects the others in the "Slot" ref. But the problem is that I also need to update two more values in the "User" directory and I can't find a way to do it "only when the user occupied the slot", that is, when the transaction was completed.
UPDATE:
This is the part where I check to see if the slot is open.
mySlotRef1.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Slot data = dataSnapshot.getValue(Slot.class);
state = data.getState();
if (state.equals("open")) {
saveSlot(slot);
} else {
Toast.makeText(getApplicationContext(), "Slot not available", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
This not how things are working when it comes to concurent writes. None of your slots will be closed. When you are using transactions, it means that every write that will be made in the same time by different users will be made in different thread of execution. So using transactions, it doesn't matter if two or many other users will make a write operation in the same time, you'll have a correct result.
If you want to update otrher fields within another class, just put your logic inside onComplete() method. This method is triggered once the transaction is complete.

Race around condition for particular case firebase

-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.

Categories

Resources