15

I am making an Android application using Firebase realtime database. When a new user registers on my app, that user's data is saved in the Firebase database.

A user has to provide the following details to register:

  1. Full Name
  2. Email
  3. Username
  4. Password

Database Structure

enter image description here

Whenever a new user tries to register, I have to make sure that each user's username is unique so I check the database if the username entered by the user already exists in the database or not.

To do this, I wrote the following method:

private boolean usernameExists(String username) {
     DatabaseReference fdbRefer = FirebaseDatabase.getInstance().getReference("Users/"+username);
     return (fdbRefer != null);
}

My logic behind this method is that if getReference method cannot find reference to the specified path, it will return null so that I can return whether fdbRefer is null or not. If fdbRefer is null, then it means that username doesn't exist in the database.

Problem

Problem with this method is that it always returns true whether the entered username exists in the database or not. Which led me to believe that fdbRefer is never null.

This brings me to my question...

Question

What does getReference method return when it can't find the specified path in the firebase database and what's the correct way to check if the username already exists in the database or not?

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193

6 Answers6

31

To check the existence of a user, please use the below code:

DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference userNameRef = rootRef.child("Users").child("Nick123");
ValueEventListener eventListener = new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        if(!dataSnapshot.exists()) {
            //create new user
        }
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        Log.d(TAG, databaseError.getMessage()); //Don't ignore errors!
    }
};
userNameRef.addListenerForSingleValueEvent(eventListener);

You can also use a Query to achieve the same thing like this:

DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
Query query = rootRef.child("Users").orderByChild("userName").equalTo("Nick123");
query.addValueEventListener(/* ... */);

This is another approach which is looping through the entire Users node but is not just using a direct reference to a single user. This option is more likely to be used when you are using as a unique identifier beteeen users the uid instead of the user name (as you do right now). So if your database structure might looks similar to this:

Firebase-root
   |
   --- Users
        |
        --- uid
             |
             --- userName: "Test User"
             |
             --- emailAddress: "user@email.com"

The second solution is the recommended one.

There is also another solution which involves you to create another node named userNames, in which you can hold only the unique user names. Please also find below the corresponding security rules:

"Users": {
  "$uid": {
    ".write": "auth !== null && auth.uid === $uid",
    ".read": "auth !== null && auth.provider === 'password'",
    "userName": {
      ".validate": "
        !root.child('userNames').child(newData.val()).exists() ||
        root.child('userNames').child(newData.val()).val() == $uid"
    }
  }
}

But since in this case, your user name is already the name of the node, I recommend you go ahead with the first one.

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • 3
    Please see also the difference between this two approaches. Using the first solution will help you save bandwidth. – Alex Mamo Dec 19 '17 at 19:34
  • Can you please explain what `if(!dataSnapshot.exists())` is for ? –  Dec 19 '17 at 19:35
  • 1
    The `dataSnapshot` object represents a snapshot at the following location: `Firebase-root -> Users -> Nick123`. The best solution is to use `exists()` method directly on the `dataSnapshot` to check for existens. When you are using a Query, you are looping through the whole object and this means waste of bandwidth. I gave you also the Query solution, maybe you'll need in the future. – Alex Mamo Dec 19 '17 at 19:44
  • @AlexMamo for the first solution, what if users node has push tokens before username e.g. when we add users with push() method. I think My dataSnapShot always gets null because of this – ninbit Aug 09 '18 at 16:29
  • @ninbit If it has used the pushed id then you should change this `.child("Nick123")` into this: `.child(pushedId)`. But to store user details under a pushed id is not a very good practice. You should use the `uid` like this: String uid = FirebaseAuth.getInstance().getCurrentUser().getUid(); and `rootRef.child("Users").child(uid)`, right? – Alex Mamo Aug 09 '18 at 16:49
  • @AlexMamo well then how to add _uid_ instead of _pushedId_? when I use push() method, it automatically generates _pushedId_ under _users_ and then adds the user data under that _pushedId_ – ninbit Aug 09 '18 at 17:00
  • @ninbit The question that you are asking is not related to this one. In order to follow the rules of this comunity, please post another fresh question, so me and other users can help you. – Alex Mamo Aug 09 '18 at 17:03
  • @AlexMamo I wait for your solution at: [this](https://stackoverflow.com/questions/51772812/checking-if-a-user-already-exists-at-firebase-realtime-database) – ninbit Aug 09 '18 at 17:30
  • @AlexMamo How do you know that first solution is not looping but the second is? Can you share any Firebase docs that confirm that? Thanks. – Red M Sep 19 '18 at 19:51
  • 1
    @RedM First solution is using a reference that is pointing directly to a specific user while the second is using a query that searches the entire object to find the corresponding user. – Alex Mamo Sep 19 '18 at 20:08
  • i am doing push value not add value like that here is pushed so i get uniq id for that node then how can i check user exist or not ? – Mehul Tank Aug 13 '19 at 06:33
  • 2
    @MehulTank In that case you should use Peter's answer. – Alex Mamo Aug 13 '19 at 06:40
  • @AlexMamo: Which of those approaches (first appraoch and query appraoch) saves more bandwith? And in the query approach what do I have to write into the brackets `query.addValueEventListener(/* ... */);`? Further, when using the query approach together with the interface for reading the data that you explained in this video https://www.youtube.com/watch?v=OvDZVV5CbQg&ab_channel=AlexMamo, where shall I execute the query statement? Inside the `readData` method from the video? – VanessaF Dec 05 '21 at 10:00
  • @VanessaF 1) If the query returns a single object, then you'll use the same bandwidth. 2) You have to add a ValueEventListener object. 3) I'm not sure I understand the question. But if you have further questions, please post a new question, here on StackOverflow, using its own [MCVE](https://stackoverflow.com/help/mcve), so I and other Firebase developers can help you. – Alex Mamo Dec 06 '21 at 07:56
  • @AlexMamo: Thanks for your answer. When using your approach from this video (https://www.youtube.com/watch?v=OvDZVV5CbQg&ab_channel=AlexMamo) the whole sub-database users is returned. When using the query approach it always returns an empty object. This is why I have 2 questions: 1) Do you also have a video where you explain in Java how to handle the query approach asynchronously? 2) Is your query `Query query = rootRef.child("Users").orderByChild("userName").equalTo("Nick123");` quering one-level below "Users" (so all User entries) or 2 levels (will it find Nick123 or "Nick123")? – VanessaF Dec 11 '21 at 10:12
  • @VanessaF I don't have a video regarding a query, but it should behave the same. If you have a hard time implementing that, please post a new question, here on StackOverflow, using its own [MCVE](https://stackoverflow.com/help/mcve), so I and other Firebase developers can help you. 2) Yes, it only queries one level below. – Alex Mamo Dec 11 '21 at 12:10
  • @AlexMamo: Thanks for your answer. To be totally honest so far your answer has not helped me. I am still trying to transfer it to my case (but I upvoted your video on Youtube). You said that the query queries only one level below. How can I query two levels below meaning that every User whose attribute "username" is equal to "Nick123" should be returned? Or each user, whose attribute "password" is "111111" should be returned. I think this is the problem I am facing with the query. If I can solve that, I will upvote your answer. – VanessaF Dec 11 '21 at 14:56
  • @VanessaF To understand 100% your use case, please post a new question, here on StackOverflow, using its own [MCVE](https://stackoverflow.com/help/mcve), so I and other Firebase developers can help you. – Alex Mamo Dec 12 '21 at 08:30
3

Try this:

DatabaseReference ref=FirebaseDatabase.getInstance().getReference().child("Users");
ref.orderByChild("username").equalTo(Nick123).addValueEventListener(new ValueEventListener(){
  @Override
  public void onDataChange(DataSnapshot dataSnapshot){
      if(dataSnapshot.exist() {
         //username exist
          }
        }

You have to use orderbychild and equalto to check the value if it is there. If you dont use orderbychild and equalto then it will just check if username child node is there and doesnt care about the value.

this orderByChild("username").equalTo(Nick123) is like saying:

WHERE username=Nick123
Peter Haddad
  • 78,874
  • 25
  • 140
  • 134
3

Works Perfectly for me

DatabaseReference reference = FirebaseDatabase.getInstance().getReference();
        Query query = reference
                .child(getString(R.string.dbname_users))
                .orderByChild("username")
                .equalTo(username);
        query.addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
                if(dataSnapshot.getChildrenCount()>0) {
                    //username found

                }else{
                    // username not found
                }

            }
            @Override
            public void onCancelled(DatabaseError databaseError) {

            }
        });
2

Instead of checking for the exists of the reference you can use orderBy query to check whether username exists already

orderByChild('username').equalTo(username) query would return data if some data already exists else it will return null.

sivaram636
  • 419
  • 3
  • 14
2

Check it like this...

    fdbRefer.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
if(dataSnapshot.exist() {
//username exist
}
else {
//username doesn't exist
}
}
});
Amit Joshi
  • 126
  • 1
  • 7
2

Since you have already got a solution to your problem, I will try to explain why your code was not working.

 DatabaseReference fdbRefer = FirebaseDatabase.getInstance().getReference("Users/"+username);

You might be thinking that in the above statement fdbRefer will be null if "Users/"+username is not present in the database.
But actually, this statement will only store a path to the specified field in getReference(). So when you check for fdbRef!=null, it is always true because fdbRefer will hold value something like 'https://.firebase.com/Users/Nick123.

Vivek
  • 106
  • 1
  • 12