Firebase rule to only allow one update in android studio - android

I'm creating an android studio voting application. It is using recyclerview to render candidates information from the database. Once the voter clicks on a vote button, the candidate is added a vote on the firebase realtime database.
I wanted to make sure that a voter can only vote once. Is there a firebase rule I can use or do I have to do it in code?
public void onBindViewHolder(#NonNull final MyViewHolder holder, final int position) {
holder.name.setText(candidates.get(position).getFirstname());
holder.party.setText(candidates.get(position).getParty());
holder.category.setText(candidates.get(position).getCategory());
Picasso.get().load(candidates.get(position).getImageurl()).into(holder.profilepic);
holder.vote.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
updateTotalVotes("increaseTotalVotes", candidates.get(position).getImageurl());
}
});
}
public static void updateTotalVotes(final String operation, String key) {
System.out.println("Inside updateTotalVotes");
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference totalVotesRef = rootRef.child("candidates").child(key).child("totalVotes");
totalVotesRef.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
System.out.println("Inside Transactions");
Integer votes = mutableData.getValue(Integer.class);
if (votes == null) {
System.out.println("Inside first if statement = null");
return Transaction.success(mutableData);
}
if (operation.equals("increaseTotalVotes")) {
System.out.println("Inside update Votes by adding 1");
mutableData.setValue(votes + 1);
} else if (operation.equals("decreaseTotalVotes")){
mutableData.setValue(votes - 1);
}
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) {
// Log.d(TAG, databaseError.getMessage()); //Don't ignore errors!
}
});
}

Firebase's security rules cannot enforce unique value in a specific property under a single node. But (as is often the case with NoSQL databases) you can use a specific data model to implement the use-case.
The usual solution for this is to use the UID of the voter as the key.
votes
uid1: "candidate A"
uid2: "candidate B"
uid3: "candidate A"
Since keys must be unique in a JSON object, this structure ensures by definition that each UID can only vote once.
This is separate from keeping the total votes for a candidate. For that you can either use security rules, or Cloud Functions.
Doing this is in security is appealing, since it means you won't need any server-side code. But the rules can become quite complex. For an example of this, see my answer to this question: Is the way the Firebase database quickstart handles counts secure?
The simpler, and these days more common, approach is to do this with a Cloud Function. From a recent project I worked on, I have this Cloud Function:
exports.countVote = functions.database.ref('/votes/{uid}').onCreate((snapshot, context) => {
let value = snapshot.val();
let countRef = snapshot.ref.parent.parent.parent.child(`totals/${value}`);
return countRef.transaction(function(current) {
return (current || 0) + 1;
})
});
So this tallies the votes for each unique value. It then ensures that users can't change their existing vote with:
{
"rules": {
"votes": {
"$uid": {
".write": "auth.uid === $uid && !data.exists()"
}
}
}
}
So a user can only vote if they user their own UID (the auth.uid variable is prepopulated and can't be spoofed), and if they haven't voted yet.

Short answer is No, you can't by the rules of db.
But you can do it with help of authentication, which user can make an account and his vote record in a sub tree of the candidate. So that the number of sub tree children is the number of votes for this candidate.
Notice: my solution could be broken if fake accounts were made, my advice using phone authentication too, to approve the account is not fake.

Related

How write user data to firebase database only once?

I want to write the user's data to the database only when it first logs on. I don't want it to be written over and over. I wrote a method for this, but I had a problem because there was more than one value in the for loop. How can I solve this problem?
public void writeFirebase() {
final Users users = new Users();
final List<String> arrayList = new ArrayList<>();
myRef.child("Users").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for (DataSnapshot ds : dataSnapshot.getChildren()) {
arrayList.add(ds.getKey());
}
if (arrayList.size() > 0){
for (String cur : arrayList) {
if (cur.matches(mUser.getUid())) {
} else {
Log.e("preee",""+cur);
if (mUser.getPhotoUrl() != null) {
users.setUserProfilPic(mUser.getPhotoUrl().toString());
}
if (mUser.getDisplayName() != null) {
users.setUserName(mUser.getDisplayName());
}
users.setUserId(mUser.getUid());
users.setUserPremium(false);
users.setUserPremiumDate("free");
users.setUserEmail(mUser.getEmail());
myRef.child("Users").child(mUser.getUid()).setValue(users);
}
}
}else {
users.setUserId(mUser.getUid());
users.setUserPremium(false);
users.setUserEmail(mUser.getEmail());
users.setUserName(mUser.getDisplayName());
users.setUserProfilPic(mUser.getPhotoUrl().toString());
users.setUserPremiumDate("free");
myRef.child("Users").child(mUser.getUid()).setValue(users);
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
if you want to write your user data in database once and prevent from writing again you just need to check if that user is already in your database. (in this answer i consider your concern is your user existence, it can be anything else for example maybe your don't want to let user update his email address if he already entered once. so you just check that if user has entered email before or not.)
for my example(user existence) i provide some links which help you check if user is already in your database.
How to search if a username exist in the given firebase database?
How to search if a username exist in the given firebase database?
How to check if a value exists in firebase database - android
just seach and find if your user is already in your database or not. check this ifs before any operation. if he was't you will allow him to continue.

Firebase iterations are slow

I'm using firebase with android to create a simple chat app. When the user chooses another user to chat with I want to check whether they've chatted together or not.
In onCreate() method I'm retrieving all the rooms that the current user used before, and I'm putting them in an arraylist called MyChatRooms<>.
Then I want to check each room to see the users of the room.
The problem is that the loop I'm using to iterate through rooms name is finishing before I'm able to retrieve any data from the database.
I know there's similar questions to mine, but none of the answers worked for me.
Here's the related code:
if (!MYChatRooms.isEmpty()) {
for (j = 0; j < MYChatRooms.size(); j++) {
roomref.child(MYChatRooms.get(j)).child("First User").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot1) {
if (!dataSnapshot1.getValue().toString().equals(Username) && dataSnapshot1.getValue().toString().equals(NUsername)) {
Users += dataSnapshot1.getValue().toString() + ",,, ";
} else if (dataSnapshot1.getValue().toString().equals(Username)) {
roomref.child(MYChatRooms.get(j)).child("Second User").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot2) {
if (dataSnapshot2.getValue().toString().equals(NUsername)) {
Users += dataSnapshot2.getValue().toString() + ",,, ";
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
}
I would suggest that you change the structure of your data. Imagine if a user has 100 chats that means your have to query 200 times to Firebase that of course does not look feasible.
What i would suggest is that your add a recentChat list in every user and whenever a user starts a new chat with someone you add the id of the second user to that list. That way you can track easily with whom the current user has interacted with.
It structure in firebase can look something like this:
User
recentChats
id of the other user
Try to change you database hierarchy or use firestore instead of real time database
Please check the following topic: https://firebase.google.com/docs/database/usage/optimize?
In my case, I had added and index column and limited the query in Firebase Rules.

Android - Firebase Database

My application was token generation mobile application. Iam using Firebase to store my data. The token were generated with the help of childrens count in firebase database. The problem I faced was if two users book their appointment at same time then both the users get same token number. For example, the number of childrens in firebase was 5. If the users A and B book their appointment then both the users get the token number 6. But the other user C book the appointment(after A and B) get the correct token number 8.
My code:
int ct=1;
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot dataSnapshot1 : dataSnapshot.getChildren()) {
++ct;
Log.d("log", "onDataChange: " + dataSnapshot1.child("Appointment").child("artistName").getValue());
}
}
The variable ct represents the token number.
What was the solution to solve the above problem?
The token should not be generated based on children count under a node. It will not serve the purpose of identifying a token uniquely in your database.
As far as I could understand your problem, you want to provide a token to the users of your application for an automated appointments to some artists. In this case, I would like to suggest you to take another table in your firebase database which will store all the tokens. Tag the token number along with the artist.
So the final table structure will be something like this.
Appointment
-- Artists
-- TokenNumber
Token
--TokenNumber
If you are familiar with the relational database, you might consider thinking of your token number as a foreign key of another table which is incremented automatically each time a token is assigned to some appointment.
Hope that helps!
Update
Based on the comment, I have found a nice answer in stackoverflow regarding this. Please have a look at the answer on how to increment a value in Firebase. The key idea is to setting the value from client side with a transactional mode. I am sharing the sample code from the answer referred.
public void incrementCounter() {
firebase.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(final MutableData currentData) {
if (currentData.getValue() == null) {
currentData.setValue(1);
} else {
currentData.setValue((Long) currentData.getValue() + 1);
}
return Transaction.success(currentData);
}
#Override
public void onComplete(FirebaseError firebaseError, boolean committed, DataSnapshot currentData) {
if (firebaseError != null) {
Log.d("Firebase counter increment failed.");
} else {
Log.d("Firebase counter increment succeeded.");
}
}
});
}

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.

Avoiding duplicate usernames in Firebase Database + Java

I'm working on a small app to practice my JAVA/Firebase skills, and I have come into a roadblock. I do admit that I'm not very familiar with Firebase, and rules associated with the database portion. But I have tried looking at other SO posts and searching through documentation.
Problem:
Users create an account (through Firebase Authentication - E-mail/Password). Then they are able to create a "character" and provide this "character" with a name. So I need the "charName" to be unique. And obviously the authenticated ID is also unique already. So I need the app to tell users if the name is already taken or if it isn't, then to go ahead with adding it to the database.
Here are simplified snippits of my code:
btnCreate.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
final String charName = "MyCharactersName";
final int charID = 123;
mFirebaseDatabase.child("characters").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
if (!(snapshot.child(charName).exists())) {
Character newCharacter = new Character(charID, charName);
mFirebaseDatabase.child("characters").child(getNewCharID()).setValue(newCharacter); // add to database
Snackbar.make(findViewById(R.id.view_root), "Success", BaseTransientBottomBar.LENGTH_INDEFINITE).show();
} else {
Snackbar.make(findViewById(R.id.view_root), "Error creating a character.", BaseTransientBottomBar.LENGTH_INDEFINITE).show();
}
} else {
Snackbar.make(findViewById(R.id.view_root), "That character name is already taken.", BaseTransientBottomBar.LENGTH_INDEFINITE).show();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
Snackbar.make(findViewById(R.id.view_root), "Error - Did not connect with Database", BaseTransientBottomBar.LENGTH_INDEFINITE).show();
}
});
}
});
Currently: The app creates a new character on the database, but you can add duplicate characters. (the charID is unique by the way, I hardcoded it in the snippit above... but it is timeStamp + 4 random digits).
So that obviously happens with the default database Rules. And in the examples that I did read, it looks like I might have to modify those?
My Database structure is as such:
App / characters / charID / charName
I tried to adapt some code from this SO post: How do you prevent duplicate user properties in Firebase?
and this is what I wrote, but it doesn't work and as I admitted before, I'm not familiar with rules, so I'm not sure what I did/did wrong. haha.
{
"rules" : {
"characters" : {
"$charID" : {
".validate": "root.child('charName_lookup/'+newData.val()).val() === auth.uid"
}
},
"charName_lookup" : {
"$charName" : {
".write" : "!data.exists()",
".validate": "newData.val() === auth.uid"
}
}
}
}
If you have any questions/clarifications please let me know. I will be stepping away from the computer periodically but I will check back promptly (I hope!)
Basically, the userName or the email address should be the name of your node. Regarding rules, you can use wildcards. If you create a userName George, to verify if exists you only need to put a reference on users node and use exists() method.
Please take a look at Frank van Puffelen's explanation from this video. Even if you'll need to remodel a bit your database, remember that this is the best practice when we are talking about duplicates.

Categories

Resources