-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.
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'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.
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 am trying to implement a two player game in android using firebase(for realtime pairing).
On firebase, i have set up a node representing active players. And on each client i have a childEventListener to listen to any changes on the players node.
Suppose
Initially there is only one player (Player A).
Then 3 more players(B,C and D) got added (At the SAME TIMESTAMP).
Then, on client side
1. Each of 4 players will get notified about the changes in the node through childEventListener.
Now, What i want to achieve is "UNIQUE PAIRING" i.e I should be able to generate 2 pairs from these 4 players. (Of course, one player can't be in both the pairs)
I have written code to pair two players in a transaction block so that no two players get paired with the same player.
private void attachActivePlayersEventListener() {
Log.i(TAG, "attachActivePlayersEventListener");
if (activePlayersEventListener == null) {
activePlayersEventListener = new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
searchPlayer(dataSnapshot);
Log.i("PLAYER_ADDED ", dataSnapshot.getValue(Player.class).getName());
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
searchPlayer(dataSnapshot);
Log.i("PLAYER_CHANGED ", dataSnapshot.getValue(Player.class).getName());
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
};
mActivePlayersDbRef.addChildEventListener(activePlayersEventListener);
}
}
private void searchPlayer(DataSnapshot dataSnapshot) {
if(mPlayer.getState().equals(PlayerState.ACTIVE)) { // if the current user is active
final String oppKey = dataSnapshot.getKey();
Player oppPlayer = dataSnapshot.getValue(Player.class);
if (oppPlayer.getState().equals(PlayerState.ACTIVE)
&& !oppKey.equals(pushId)) { // if the opponent chosen is not the current user
DatabaseReference oppRef = mActivePlayersDbRef.child(oppKey);
oppRef.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
Player player2 = mutableData.getValue(Player.class);
if (player2 == null) {
return Transaction.success(mutableData);
}
mActivePlayersDbRef.child(pushId).child("state").setValue(PlayerState.PLAYING);
mActivePlayersDbRef.child(oppKey).child("state").setValue(PlayerState.PLAYING);
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) {
Log.d("PAIRING", "pairing:onComplete:" + databaseError);
}
});
}
}
}
My DOUBT is
Is this the correct way of pairing?
What happens when PlayerA tries to pair up with PlayerB, PlayerB tries to pair up with PlayerC and PlayerC tries to pair up with PlayerD and finally PlayerD tries to pair up with PlayerA(In a CYCLIC way). How to make sure this case doesn't happen?
Note that in Doubt2, I am not taking care of the condition where two players try to pair up with the same player. (As transaction block will ensure that it won't happen, I guess :/ ).
What you are trying to do is difficult to do correctly and safely. It's better to let a backend service do this matching so that the clients don't all have to figure out how to agree with each other somehow.
You can use Cloud Functions for Firebase to write a database trigger that responds to changes in your database. One strategy would be for clients to push data into a location in the database to indicate their intent to be matched. Then, when a function triggers on those writes, it can check to see if there are other suitable players to be matched, and write more data into the database to set up the game. The clients will also need a way to listen for the game starting up after they have been matched. This is still all very much non-trivial, but a lot easier than putting the logic in the clients.
In my application I try to call Notifications with the following code:
ref.orderByChild("uid").equalTo(uid).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Long number = dataSnapshot.getChildrenCount();
if (number == 0){
// no updates
return;
}
else {
showNotification();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
My problem is that Notifications are shown although there are no Updates in my Firebase-Database (especially for the retrieved Data). Why is the ValueEventListener running so often and how can I solve my issue? The 'number' is always the number of all assigned books from the logged-in User...
Maybe there is another smarter solution to get push-notifitcations.
Instead of addvalueeventlistener() use addListenerForSingleValueEvent