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.
Related
I want to make public only username node under the user's child for register function. So this value must be able for the non-register users to register. Here is my firebase database structure. How can I do that?
Here are my rules
{
"rules": {
"Homeland": {
".indexOn": ["username","email","bakiyetl","yarismada","yarismadabb","splashmesaj","uygulama1tut","uygulama2tut","uygulama3tut","uygulama4tut","uygulama5tut","uygulama6tut","uygulama7tut","uygulama8tut","uygulama9tut","uygulama10tut"]
},
"gunluksifreler": {
".read": true, // <-- allows every person
".write": true
},
".read": "auth !== null", // <-- allows read if logged in
".write": "auth !== null" // <-- allows write if logged in
}
}
Search username in database.
Query cmyquery = refbir.child("Homeland").orderByChild("username").equalTo(usergivenname);
cmyquery.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()){
//user name already taken
}else if(sonlandirgorev!=1){
//You can use this username
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Rule 1: To be able to read a location or run a query on a location, you must be able to read the data on that location.
Knowing that, let's look at:
Query cmyquery = refbir.child("Homeland").orderByChild("username")
This code requires that the user can read the Homeland node.
This is referred to in the documentation as rules are not filters.
Rule 2: One you can read or write data at a certain level in the JSON, that permission can't be taken away at a lower level.
Given this rule and the above: one a user can read and query Homeland they can read all data below it. There is no way to hide part of that data.
This is referred to in the documentation as read and write rules cascade.
The common solution is to split the data by who needs access to it. So if you want only the username property values to be available to everyone, create a top-level usernames node with the same keys under it, and then just the value of that user's user name. On that new usernames node you can then grant more liberal access than on the Homeland node.
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.
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.
I'm still on Firebase and this time I have a question related on the deletion of objects.
I have a structure like the following:
users: {
UsErId1:{
name: "Jack",
email: "m#i.l"
},
UsErId2: { + },
UsErId3: { + }
},
user_contacts: {
UsErId1:{
UsErId2: true,
UsErId3: true
},
UsErId2: {
UsErId1: true
}
}
So if I want to delete an user I have to:
Delete the user object
Delete the user object under the user_contacts branch
Remove all the indexes from user_contacts that are pointing to that user
My performance problems comes from the point 3, because I need to iterate all the user_contacts entries to see if a user it's present in the childrens.
An example of code is the following:
private void deleteUser(String userId) {
firebaseDatabase.getReference("users").child(userId).removeValue();
firebaseDatabase.getReference("users_contacts").child(userId).removeValue();
firebaseDatabase.getReference("users_contacts").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot usersSnapshot : dataSnapshot.getChildren()) {
for( DataSnapshot contactSnapshot : usersSnapshot.getChildren() ){
String contactId = contactSnapshot.getValue(String.class);
if( contactId.equals(userId) ){
contactSnapshot.getRef().removeValue();
}
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
I've thought of two possible solutions:
Solution 1: Don't delete the indexes from user_contacts and when I've to load the user contacts, I've to do a call to each user to see if the user is null (has been deleted), and in that case, don't show it. By the way, this results in a dirty database.
Solution 2: create a reverse index contacts_users where I store the users for which the user I'm trying to delete is referenced. As follow:
contacts_user: {
UsErId1: {
UsErId2: true
},
UsErId2: {
UsErId1: true
},
UsErId3: {
UsErId1: true
}
}
So, when I have to delete a user, I will look at its childs in contacts_users and know every users that has it in its contacts, and proceed to delete it (now that I can know the entire path). This seems to me to be a more NoSql-like approach.
What do you think? Is there another way to do it?
Your second solution is how I would suggest doing it, no need to search. You could store that information on a per user basis, but if it grows too large having it elsewhere is better.
Likewise deleting in the other direction also becomes easier.
A bit confused why there's no display name being displayed in my Firebase database, I've followed the steps in the example on their website and didn't get the expected result which they showed. Here's the example code.
final Firebase ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com");
ref.authWithPassword("jenny#example.com", "correcthorsebatterystaple",
new Firebase.AuthResultHandler() {
#Override
public void onAuthenticated(AuthData authData) {
// Authentication just completed successfully :)
Map<String, String> map = new HashMap<String, String>();
map.put("provider", authData.getProvider());
if(authData.getProviderData().containsKey("displayName")) {
map.put("displayName", authData.getProviderData().get("displayName").toString());
}
ref.child("users").child(authData.getUid()).setValue(map);
}
#Override
public void onAuthenticationError(FirebaseError error) {
// Something went wrong :(
}
});
Whenever I tried this example code the expected result was to be something similar to what is shown below.
{
"users": {
"6d914336-d254-4fdb-8520-68b740e047e4": {
"displayName": "alanisawesome",
"provider": "password"
},
"002a448c-30c0-4b87-a16b-f70dfebe3386": {
"displayName": "gracehop",
"provider": "password"
}
}
}
Instead of showing something similar to the above, in my database only the provider is shown and not the displayName. Is there any necessary steps required to get the displayName to show or should it be there automatically?
I tried to look into this to see what was included within authData.getProviderData() and this was what was produced in the console when I printed it out.
01-25 17:58:51.921 15651-15651/<package-name> I/System.out: {email=j.joe#hotmail.co.uk, isTemporaryPassword=false, profileImageURL=https://secure.gravatar.com/avatar/de53da9874178adb1a44b392ba5bed2f?d=retro}
So from the result produced it's left me wondering if the key "displayName" was formerly automatically created from the email and stored by default in authData.getProviderData() and that's why it is no longer shown.
Would appreciate it if someone could explain where the displayName in the database comes from because judging by the expected result it seems I've missed something.
Thanks!
An email+password account does not have an associated display name. It only requires an email address and a password.
You can definitely require the user to enter additional information. Just don't pass it to createUser() and don't overwrite it after calling authWithPassword().
Update: since the update to Firebase Authentication released at Google I/O 2016, there is now a display name property for each user. So you can also keep the display name in Firebase Authentication itself (although you won't be able to query/search for it).