I'm confused here and really at the end on the line, I have set up a like function in my project. if a user presses the like button once, the like counter update from 0 to 1(liked) and the like imageButton(change color) updates successful. if pressed twice the counter updates from 1 to 0(unlike) successful.
The problem is when a different user also press the like button to like the same post, the like counter does not update from 1 to 2. Please help. I hope this is clear. Below is the code.
viewHolder.mLikebtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mProcessLike = true;
mDatabaseLikeCount = FirebaseDatabase.getInstance().getReference().child("Notes").child(post_key).child("likecount");
mDatabaseLikeCount.keepSynced(true);
mDatabaseLike.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (mProcessLike) {
if (dataSnapshot.child(post_key).hasChild(auth.getCurrentUser().getUid())) {
Log.i("D Diary", "User has already Liked. So it can be considered as Unliked.");
mDatabaseLike.child(post_key).child(auth.getCurrentUser().getUid()).removeValue();
mDatabaseLikeCount.setValue(likeCount = likeCount - 1 );
mProcessLike = false;
} else {
Log.i("D Diary", "User Liked");
mDatabaseLike.child(post_key).child(auth.getCurrentUser().getUid()).setValue(auth.getCurrentUser().getDisplayName());
mDatabaseLikeCount.setValue(likeCount = likeCount + 1 );
Log.i(dataSnapshot.getKey(), dataSnapshot.getChildrenCount() + "Count");
mProcessLike = false;
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
});
This should work. You should use a transaction to increment or reduce a value that multiple people will be interacting with.
if (dataSnapshot.child(post_key).hasChild(auth.getCurrentUser().getUid())) {
Log.i("D Diary", "User has already Liked. So it can be considered as Unliked.");
mDatabaseLike.child(post_key).child(auth.getCurrentUser().getUid()).removeValue();
updateCounter(false);
mProcessLike = false;
} else {
Log.i("D Diary", "User Liked");
mDatabaseLike.child(post_key).child(auth.getCurrentUser().getUid()).setValue(auth.getCurrentUser().getDisplayName());
updateCounter(true)
Log.i(dataSnapshot.getKey(), dataSnapshot.getChildrenCount() + "Count");
mProcessLike = false;
}
With updateCounter:
private void updateCounter(bool increment) {
mDatabaseLikeCount.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
if (mutableData.getValue() != null) {
int value = mutableData.getValue(Integer.class);
if(increment) {
value++;
} else {
value--;
}
mutableData.setValue(value);
}
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b,
DataSnapshot dataSnapshot) {
// Transaction completed
Log.d(TAG, "likeTransaction:onComplete:" + databaseError);
}
});
}
Firebase Transactions
Related
I would like to allow the users to change their password before logging in. In practice I have LoginActivity that with a clickable textview redirects to an activity where the user enters his email and the new password to be set. The problem is that when the user tries to log in, it fails. Is it possible to do this or do I have to change the method?
This is my code:
private EditText emailRetrieve, firstPassword, passwordConfirm;
private Button resetPasswordBtn;
private String email, password, passwordToConfirm = "";
private FirebaseAuth mAuth;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_set_password);
uploadUI();
mAuth = FirebaseAuth.getInstance();
resetPasswordBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
setNewPassword();
}
});
}
// Validation email and password
private boolean validation() {
boolean valid = true;
email = emailRetrieve.getText().toString();
password = firstPassword.getText().toString();
passwordToConfirm = passwordConfirm.getText().toString();
if (email.isEmpty() || !android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
emailRetrieve.setError("Insert valid email address");
valid = false;
} else {
emailRetrieve.setError(null);
}
if (password.isEmpty() || password.length() < 8) {
firstPassword.setError("Insert valid password");
valid = false;
} else {
firstPassword.setError(null);
}
if(passwordToConfirm.isEmpty() || (!passwordToConfirm.equals(password))) {
passwordConfirm.setError("Passwords must be equals");
valid = false;
} else {
passwordConfirm.setError(null);
}
return valid;
}
private String getPasswordToConfirm(TextView textView) {
String confirm = textView.getText().toString();
return confirm;
}
private void setNewPassword() {
if(!validation())
return;
Utils.loadProgressDialog(SetPasswordActivity.this, "Uploading...");
//progressBar.setVisibility(View.VISIBLE);
FirebaseDatabase database = FirebaseDatabase.getInstance();
final DatabaseReference myRef = database.getReference().child("users");
myRef.orderByChild("email").equalTo(email).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
if(snapshot.getValue() != null) {
for(DataSnapshot datasnap : snapshot.getChildren()) {
if(datasnap.child("email").getValue().toString().equals(email)) {
datasnap.child("password").getRef().setValue(getPasswordToConfirm(passwordConfirm))
.addOnSuccessListener(new OnSuccessListener() {
#Override
public void onSuccess(Object o) {
Toast.makeText(SetPasswordActivity.this, "Password successfully changed",
Toast.LENGTH_SHORT).show();
}
});
}
}
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
throw error.toException();
}
});
startActivity(new Intent(getApplicationContext(), LoginActivity.class));
}
#Override
public void uploadUI() {
emailRetrieve = findViewById(R.id.email_retrieve);
firstPassword = findViewById(R.id.first_password);
passwordConfirm = findViewById(R.id.password_confirm);
resetPasswordBtn = findViewById(R.id.reset_password_btn);
}
Thanks in advance to everyone!
I guess, you are navigating user to LoginActivity before the password is changed, Firebasedatabase call is asynchronous and you have to take user to LoginActivity only on Success, that is when you are showing toast to user. Basically move startActivity in setNewpassword to onSuccess of firebase query
private void setNewPassword() {
if(!validation())
return;
Utils.loadProgressDialog(SetPasswordActivity.this, "Uploading...");
FirebaseDatabase database = FirebaseDatabase.getInstance();
final DatabaseReference myRef = database.getReference().child("users");
myRef.orderByChild("email").equalTo(email).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
if(snapshot.getValue() != null) {
for(DataSnapshot datasnap : snapshot.getChildren()) {
if(datasnap.child("email").getValue().toString().equals(email)) {
datasnap.child("password").getRef().setValue(getPasswordToConfirm(passwordConfirm))
.addOnSuccessListener(new OnSuccessListener() {
#Override
public void onSuccess(Object o) {
startActivity(new Intent(getApplicationContext(), LoginActivity.class));
Toast.makeText(SetPasswordActivity.this, "Password successfully changed",
Toast.LENGTH_SHORT).show();
}
});
}
}
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
throw error.toException();
}
});
}
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 have a db like this :
And I want to update value of cityIdealScore to "cityTop*10 + cityWin" in firebase DB automatically but I don't know how to do.
p.s. I need value of cityIealScore to query DB by Score
p.s.2
public void fbWinUpdate(int cityNum) {
String cNum = String.valueOf(cityNum);
databaseReference.child(cNum).runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
CityDetails cd = mutableData.getValue(CityDetails.class);
if(cd == null) {
return Transaction.success(mutableData);
}
cd.cityWin = cd.cityWin + 1;
mutableData.setValue(cd);
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) {
Log.d("DBError", "postTransaction:onComplete:" + databaseError);
}
});
}
public void fbTopUpdate(int cityNum) {
String cNum = String.valueOf(cityNum);
databaseReference.child(cNum).runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
CityDetails cd = mutableData.getValue(CityDetails.class);
if(cd == null) {
return Transaction.success(mutableData);
}
cd.cityTop = cd.cityTop + 1;
mutableData.setValue(cd);
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) {
Log.d("DBError", "postTransaction:onComplete:" + databaseError);
}
});
}
public void fbTop4Update(int cityNum) {
String cNum = String.valueOf(cityNum);
databaseReference.child(cNum).runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
CityDetails cd = mutableData.getValue(CityDetails.class);
if(cd == null) {
return Transaction.success(mutableData);
}
cd.cityTop4 = cd.cityTop4 + 1;
mutableData.setValue(cd);
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) {
Log.d("DBError", "postTransaction:onComplete:" + databaseError);
}
}); }
It's my cityTop, cityWin, cityTop4's update code. I think I can calculate Score in this method, but I think it's ineffective. I wonder that I can calculate Score at FirebaseRules and so on
This is how you can do it:
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference ref = rootRef.child("City_Details_Database");
ValueEventListener eventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot ds : dataSnapshot.getChildren()) {
Integer cityTop = ds.child("cityTop").getValue(Integer.class);
Integer cityWin = ds.child("cityWin").getValue(Integer.class);
Integer cityIdealScore = cityTop*10 + cityWin;
dataSnapshot.child("cityIdealScore").getRef().setValue(cityIdealScore);
Log.d("TAG", valueToUpadate);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {}
};
ref.addListenerForSingleValueEvent(eventListener);
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.
I have a Firebase listener that checks if User exists (Im making login/signup using Firebase database) in order to tell if the username is taken or not, the problem is that it takes 2 clicks on the Signup button for it to work, because the listener cant tell if username exists or not fast enough, only on second click when it already decided for the first click it is possible to signup, but then again it does not really check the username of the second click (If I change username now, even if it is taken it will work)
database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference("Users/" + usernameEt.getText().toString() +"/password");
myRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String value = dataSnapshot.getValue(String.class);
if(value != null) {
Toast.makeText(getApplicationContext(), "Username Taken", Toast.LENGTH_SHORT).show();
clear = false;
}else
clear = true;
}
#Override
public void onCancelled(DatabaseError error) {
// Failed to read value
clear = false;
Toast.makeText(getApplicationContext(), "Internet Error", Toast.LENGTH_SHORT).show();
}
});
//insert check if clear has value
if(!clear) //TODO FIX takes time to the listener to do the job
return false;
clear is a Boolean type var and is null at the first time this code runs, this code is for checking if username is taken or not
Maybe your code looks like this:
#Override
public void onCreate(Bundle savedInstanceState) {
...
signUpBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (checkUserExists()) {
...
} else {
signUp();
}
}
}
}
private boolean checkUserExists() {
database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference("Users/" + usernameEt.getText().toString() +"/password");
myRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String value = dataSnapshot.getValue(String.class);
if(value != null) {
Toast.makeText(getApplicationContext(), "Username Taken", Toast.LENGTH_SHORT).show();
clear = false;
}else
clear = true;
}
#Override
public void onCancelled(DatabaseError error) {
// Failed to read value
clear = false;
Toast.makeText(getApplicationContext(), "Internet Error", Toast.LENGTH_SHORT).show();
}
});
//insert check if clear has value
if(!clear) //TODO FIX takes time to the listener to do the job
return false;
}
private void signUp() {
...
}
You should change it to:
#Override
public void onCreate(Bundle savedInstanceState) {
...
signUpBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
checkUserExists();
}
}
}
private boolean checkUserExists() {
database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference("Users/" + usernameEt.getText().toString() +"/password");
myRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String value = dataSnapshot.getValue(String.class);
if(value != null) {
Toast.makeText(getApplicationContext(), "Username Taken", Toast.LENGTH_SHORT).show();
} else {
signUp(); // sign up here
}
}
#Override
public void onCancelled(DatabaseError error) {
Toast.makeText(getApplicationContext(), "Internet Error", Toast.LENGTH_SHORT).show();
}
});
}
private void signUp() {
...
}
Also you can block user input while checking user data:
private boolean checking = false; // added
#Override
public void onCreate(Bundle savedInstanceState) {
...
signUpBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (checking) return; // added
checkUserExists();
checking = true; // added
}
}
}
private boolean checkUserExists() {
database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference("Users/" + usernameEt.getText().toString() +"/password");
myRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
checking = false; // added
String value = dataSnapshot.getValue(String.class);
if(value != null) {
Toast.makeText(getApplicationContext(), "Username Taken", Toast.LENGTH_SHORT).show();
} else {
signUp(); // sign up here
}
}
#Override
public void onCancelled(DatabaseError error) {
checking = false; // added
Toast.makeText(getApplicationContext(), "Internet Error", Toast.LENGTH_SHORT).show();
}
});
}
private void signUp() {
...
}
Hope it works.