I have a problem with my Firebase project. I am trying to add a reaction button to it, so what I did is everytime a user presses love/like button, the firebase node which has "ReactingUser" as node, it will add the userID as Key and "1" as value. Also, the total number of reactions on the post is denoted by "TotalReactions" node which will count the total number of children of "ReactingUser" and set the value accordingly. My question is that, what if two user press it at the same time, I know it will cause collision and I may face problem because my data is depended on that "TotalReactions" node. Can anyone tell me how can I use transactions on my database so that I won't have collision problem? I tried going through other tutorials, but none of them clarify me. This is how my code for reaction button looks like:
reactionUserDatabase.child(uniqueKey).child("ReactingUser").child(currentUserID).setValue("1").addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
reactionUserDatabase.child(uniqueKey).child("ReactingUser").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String totalVotes = String.valueOf(dataSnapshot.getChildrenCount());
reactionUserDatabase.child(uniqueKey).child("TotalReactions").setValue(totalVotes).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
} else {
Toast.makeText(context, "Some Error Occured", Toast.LENGTH_LONG).show();
}
}
});
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Now how can I implement the data transactions to prevent collision? Or am I going a good go and it won't effect ?
This is how you can use transactions:
reactionUserDatabase.child(uniqueKey).child("ReactingUser").child(currentUserID).runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
String stringTotalVotes = mutableData.getValue(String.class);
int totalVotes = Integer.valueOf(stringTotalVotes);
if (totalVotes == null) {
return Transaction.success(mutableData);
}
if (totalVotes != null) {
totalVotes = totalVotes - 1;
} else {
totalVotes = totalVotes + 1;
}
mutableData.setValue(String.valueOf(totalVotes));
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) {
Log.d(TAG, "postTransaction:onComplete:" + databaseError);
}
});
As you see, this code is using the String class but I strongly recommend you to set the value of your currentUserID field as an int and not as a String.
Related
There can be mutiple people on the app click a button that will Increment value in firebase and get that value back, but if each person clicks the button at the same time right now, they will get the same value, how do I Increment with each person getting a differnt value?
DatabaseReference reference = FirebaseDatabase.getInstance().getReference("queue");
reference.child(queue_id).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
currentNum = (long) dataSnapshot.child("currentNumber").getValue();
++currentNum;
dataSnapshot.child("currentNumber").getRef().setValue(++currentNum);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
To prevent conflicts from multiple users updating the same node at (almost) the same time, you need to use a transaction. In your case that'd look something like:
DatabaseReference reference = FirebaseDatabase.getInstance().getReference("queue");
DatabaseReference numberRef = reference.child(queue_id).child("currentNumber");
numberRef.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
Long value = mutableData.getValue(Long.class);
if (value == null) {
mutableData.setValue(1);
}
else {
mutableData.setValue(value+1);
}
return Transaction.success(mutableData);
}
}
#Override
public void onComplete(DatabaseError databaseError, boolean b,
DataSnapshot dataSnapshot) {
// Transaction completed
Log.d(TAG, "runTransaction:onComplete:" + databaseError);
}
As per your code, you're incrementing the counter by 1 but you are not posting the same in the database. Use HashMap to update the data in your database when you modify it.
You can try the below code:
button.setOnClickListerner(new View.onClickListener() {
DatabaseReference reference = FirebaseDatabase.getInstance().getReference("queue");
reference.child(queue_id).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
currentNum = (long) dataSnapshot.child("currentNumber").getValue();
long newValue = currentNum + 1;
HasMap<String, Object> hashMap = new HashMap<>();
hashMap.put ("currentNumber", newValue);
reference.child(queue_id).updateChildren(hashMap);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
};
Hope this works!
You can use method transactions. Refer to this link: https://firebase.google.com/docs/database/android/read-and-write#save_data_as_transactions"
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);
}
});
}
I want to read 2 values from Firebase (if exist as in first user there are not) and if needed to update them. Actually i m trying at first to do it with one value, with no luck. My code is
mAuthListener = new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth) {
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (user != null) {
final String uid = Objects.requireNonNull( mAuth.getCurrentUser() ).getUid();
mScoreReference.child( uid ).addListenerForSingleValueEvent( new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
User oldscore = dataSnapshot.getValue( User.class );
if (dataSnapshot.exists()) {
if(oldscore.getScore()==null){
oldscore.setScore(String.valueOf( 0));
String oldscorevalue = Objects.requireNonNull(oldscore).getScore();
int convertedscore = Integer.parseInt(oldscorevalue);
if (convertedscore > 0) {
//Upload points to Database
mScoreReference.child(uid).child( "Score" )
.setValue( convertedscore + newScore );
} else mScoreReference.child(uid).child( "Score" ).setValue( newScore );
}
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
} );
I managed to convert null(the first value as score is not exist) and to set score. So my problem is why i cant update the value, and how can i update 2 values at the same time? Probably i have to use Transaction, but i m not familiar at all with this. I m reading, but i cant find how to convert this code to Transaction.
UPDATE
I tried with Transaction.
mScoreReference.child( uid ).runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
User user = mutableData.getValue(User.class);
if (user.getScore() == null) {
mScoreReference.child(uid).child( "Score" ).setValue( newScore );
}
else{
String oldscorevalue = Objects.requireNonNull(user).getScore();
int convertedscore = Integer.parseInt(oldscorevalue);
mScoreReference.child(uid).child( "Score" )
.setValue( convertedscore + newScore );
}
// Set value and report transaction success
mutableData.setValue(user);
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b,
DataSnapshot dataSnapshot) {
// Transaction completed
Log.d(TAG, "postTransaction:onComplete:" + databaseError);
}
});
and i have strange results. At first time do nothing, then adds Score and another irelavent User value, and after this adds 2 Score fields in Database.
After user authentication, I create the user node and an IsRegistrationCompleted variable in the Firebase Database. I then pass the check to EventMainActivity where the first thing I do is check the IsRegistrationCompleted value.
The problem I have is that when I switch to EventMainActivity the system has not yet finished inserting data into FirebaseDatabase and finding no value IsRegistrationCompleted, the system goes into error.
This is my code:
mAuth.signInWithCredential(credential)
.addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
// Sign in success, update UI with the signed-in user's information
Log.d(TAG, "signInWithCredential:success");
FirebaseUser user= FirebaseAuth.getInstance().getCurrentUser();
isNew = task.getResult().getAdditionalUserInfo().isNewUser();
if(isNew) {
prefManager.setIsFirstAccess(true);
addUserToDb(user);
}
} else {
..
}
..
}
});
}// [END auth_with_google]
...
//add user in Firebase Database
private void addUserToDb(FirebaseUser user) {
uid = user.getUid();
author.setUid(uid);
author.setEmail(user.getEmail());
author.setFull_name(user.getDisplayName());
author.setUrl_img_profile_large(user.getPhotoUrl().toString());
userRef = rootRef.child("Users").child(uid);
userRef.setValue(author).addOnCompleteListener(this,new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if(task.isSuccessful()) {
setRegistrationNotCompleted();
}
}
});
}
//insert IsRegistrationCompleted in FirebaseDatabase
private void setRegistrationNotCompleted() {
user_registration = rootRef.child("Users_Settings").child(uid).child("IsRegistrationCompleted");
user_registration.setValue(NOT_REG).addOnCompleteListener(this,new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
startActivity(newIntent(RegActivity.this,EventMainActivity.class));
finish();
}
}
});
}
At this point, entering EventMainActivity I find a java.lang.NullPointerException because it does not find the variable IsRegistrationCompleted and in fact in firebase Database the value has not been inserted yet.
//This is code when try Exception
usr_registration.child(uid).child("IsRegistrationCompleted").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Boolean isRegCompleted =(Boolean) dataSnapshot.getValue();
Log.d(TAG,"isRegcompleted "+isRegCompleted);//Here in the Log there is "isRegcompleted null"
if (isRegCompleted.equals(false)) {
launchWizardReg();
}
}
Add the check for exist in your value event listener to avoid null values, since it may take time to set the value by firebase at the node.
usr_registration.child(uid).child("IsRegistrationCompleted")
.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()){
Boolean isRegCompleted =(Boolean) dataSnapshot.getValue();
Log.d(TAG,"isRegcompleted "+isRegCompleted);
if (isRegCompleted.equals(false)) {
// remember to remove the value event listener,
//else it will be called again if the value changes
launchWizardReg();
}
}
}
Since you are using value event listener instead of single value event it will keep triggering if the value changes at this node, so remove the listener when the value required is available
I want to retrieve houfxWvbwyVmbHX60IGjpNkZR9w2 key , How to do?
Here is the code for the same
mQuery = mfollowing.orderByChild("following").equalTo(user_id);
mQuery.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
final String key = dataSnapshot.getKey();
mdatabaseUsers.child(user_id).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
DatabaseReference newfollscreen = mdatabaseFollwer.child(key).child(newPost.getKey());
newfollscreen.child("screen").setValue(downloadUrl.toString());
newfollscreen.child("engagement").setValue("NA");
newfollscreen.child("url").setValue("NA");
newfollscreen.child("uid").setValue(user_id);
newfollscreen.child("name").setValue(dataSnapshot.child("name").getValue());
newfollscreen.child("image").setValue(dataSnapshot.child("image").getValue()).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()){
Toast.makeText(AddimageActivity.this, "Posted among your followers", Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(AddimageActivity.this, "Error", Toast.LENGTH_SHORT).show();
}
}
});
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
#Override
public void onCancelled (DatabaseError
databaseError){
}
});
Here is the Firebase database
second image
enter image description here
I tried using child data snapshot method but did not worked. Any help for this will be much appreciated.
Assuming you have a DataSnapshot that points to the children rhZ4...wt2, you can call snapsthot.getRef().toString() which will return you something like https://xxx.firebaseio.com/.../houfxWvbwyVmbHX60IGjpNkZR9w2/rhZ4...wt2. From there you can extract the parent key.
From your example, after you query the database using mQuery, inside onDataChange you have a DataSnapshot.
So calling dataSnapshot.getRef().toString() will give you https://xxx.firebaseio.com/.../houfxWvbwyVmbHX60IGjpNkZR9w2/rhZ4...wt2/following.
Code to extract the parent key (put it inside your 1st onDataChange):
String reference = dataSnapshot.getRef().toString();
String[] tokens = reference.split("/");
String parentKey = tokens[tokens.length - 3];
This parentKey should be your houfxWvbwyVmbHX60IGjpNkZR9w2.
I am trying to move my data present at one node i.e cart_details/UID to another node orders/UID/order1.
I tried different ways of doing it but all seem to be a bit confusing. Is there any built-in functionality or method that could possibly make the job easier?
Any help is appreciated.
I have attached the image for the same. IMAGE.
To solve this, I recommend you use the following lines of code:
public void copyRecord(Firebase fromPath, final Firebase toPath) {
fromPath.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
toPath.setValue(dataSnapshot.getValue(), new Firebase.CompletionListener() {
#Override
public void onComplete(FirebaseError firebaseError, Firebase firebase) {
if (firebaseError != null) {
Log.d(TAG, "Copy failed!");
} else {
Log.d(TAG, "Success!");
}
}
});
}
#Override
public void onCancelled(FirebaseError firebaseError) {
Log.d("TAG", firebaseError.getMessage()); //Never ignore potential errors!
}
});
}
This is a copy and not a move operation as you probably see, so the original record will remain at its original place. If you would like to delete, you can use the removeValue() method on the from path just after the System.out.println("Success");.
Edit: (03 May 2018).
Here is the code for using the new API.
private void copyRecord(DatabaseReference fromPath, final DatabaseReference toPath) {
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
toPath.setValue(dataSnapshot.getValue()).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isComplete()) {
Log.d(TAG, "Success!");
} else {
Log.d(TAG, "Copy failed!");
}
}
});
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.d("TAG", databaseError.getMessage()); //Never ignore potential errors!
}
};
fromPath.addListenerForSingleValueEvent(valueEventListener);
}