skip to Main Content

I am creating a simple social media app in Android Kotlin with FireStore.

In the user registration page, after user inputs all the basic information I create a collection called "users" like so:

db.collection("users").document(mUser.uid).collection("basicUserInfo").add(info)

In another page where users search for other users, I am trying to read the collection I made above to read specific users data like so :

 db.collection("users").get()

but the returned result contains 0 documents in it even though in FireBase Console I can see there are 5 documents in it.

On the other hand, when I try creating a collection "users" manually on the firebase console with some documents in it, the app reads the documents successfully, returning the correct size.

So I am assuming the problem lies in the way I initiate the collection "users" from the app side.

Do you know why this doesn’t work yet when created manually it works?

2

Answers


  1. The first line of code you shared creates a document in the basicUserInfo subcollection. It does not create a document in the users collection.

    That’s why when you read all documents from the users collection in your second line of code, you get no results: you never created a document in the users collection.

    You can see this in the Firestore console, as the document ID in the users collection is shown in italics. If you search for Firestore documents in italics here on Stack Overflow, you’ll find more questions from developers about this.

    If you want to be able to read the document form the users collection, you’ll have to create a document there too. The document can be empty, but it does have to exist in order to read it.

    Login or Signup to reply.
  2. Cloud Firestore queries are shallow and this behaviour is intentional.

    Here "shallow" means that when you query for documents in the /users collection (like with db.collection("users").get()), it does just that. A document that is in a subcollection of a document in /users is considered to be in a different collection and will not be returned in that first query.

    While now a little dated, I recommend reviewing the first few videos of the Getting to Know Cloud Firestore playlist. The second video of the series covers what I’m going to cover below.

    Looking at this line:

    db.collection("users").document(mUser.uid).collection("basicUserInfo").add(info);
    

    Your database contains a /users collection that contains documents named with the User ID of each user. Each of these user documents have a /basicUserInfo subcollection that contains some basic user information in a document with an auto-generated document ID.

    Imagining that your database contains no data and looks like this:

    {
      // an empty /users collection
      "/users": []
    }
    

    Note: Empty collections don’t actually exist in Cloud Firestore. I’m showing a collection here as an empty array for the sake of illustrating your issue.

    As a user signs up, you authenticate them, collect some user info (your info object) and then store this to your database using:

    db.collection("users").document(mUser.uid).collection("basicUserInfo").add(info);
    

    Your database now looks like this:

    {
      // an empty /users collection
      "/users": [],
    
      // a subcollection called "basicUserInfo" nested under "/users/vRIos3pKKjg9mkQzGKcdtQ4Iuhp2"
      "/users/vRIos3pKKjg9mkQzGKcdtQ4Iuhp2/basicUserInfo": [ 
        {
          "__name__": "zkqoj5YpWsLs3FIDPyMy", // the auto-generated Firestore document ID from add(info)
          "firstName": "Anita",
          "lastName": "Example",
          "country": "GBR",
          "verified": false
        }
      ]
    }
    

    As can be seen above, the /users collection is still empty. This is because the user’s own document was not created or set with any data directly. This would be achieved using:

    // set with no data
    db.collection("users").document(mUser.uid).set(hashMapOf<String, Any?>())
    
    // set with some data
    db.collection("users").document(mUser.uid).set(userData)
    

    As an admin using the Firebase console for Cloud Firestore, you would want to know of these subcollections, so it displays them in its interface. This behaviour is covered by the documentation on Non-existent ancestor documents:

    Non-existent ancestor documents

    A document can exist even if one or more its ancestors don’t exist. For example, the document at path /mycoll/mydoc/mysubcoll/mysubdoc can exist even if the ancestor document /mycoll/mydoc does not. The Cloud Firestore data viewer displays non-existent ancestor document as follows:

    • In a collection’s list of documents, the document IDs of non-existent ancestor documents are italicized.
    • In a non-existent ancestor document’s information panel, the data viewer points out that the document does not exist.
      Non-existent ancestor document in the console.

    In the example we’ve walked through so far, the /users/vRIos3pKKjg9mkQzGKcdtQ4Iuhp2 document will appear in the Firebase data viewer in italics and the nested data will show in the subcollections panel along with the generated document.

    This behaviour is why your "/users" query does not return any results and is pointed out by the warning that follows the above screenshot:

    Warning: Even though non-existent ancestor documents appear in the console, they do not appear in queries and snapshots. You must create the document to include it in query results.

    When you create a document in Cloud Firestore using the data viewer, you first create the user ID document in the users collection, then start a new subcollection, and then create the basicUserInfo document.

    An important difference between your code and the manual steps is that you create the user ID document in the users collection. This is why the behaviour is different between the two approaches.

    To replicate what you are doing in the Firestore data viewer, you need to add a line to create the user ID document:

    // Important: These actions are asynchronous. Handle errors appropriately.
    
    // create the empty document
    db.collection("users").document(mUser.uid).set(hashMapOf<String, Any?>())
    
    // store the user's information
    db.collection("users").document(mUser.uid).collection("basicUserInfo").add(info)
    

    There are two ways that you can handle this problem.

    Option 1: Use a collection group query

    Using a collection group query, you can find all documents that are nested under collections called "basicUserInfo".

    db.collectionGroup("basicUserInfo").get()
    

    If you use this approach, I would recommend swapping add(info) for document(mUser.uid).set(info) so that the document ID is also the user’s ID rather than an auto-generated one.

    Option 2: Restructure

    To avoid this problem and to also keep sensitive information private, you could split the nested subcollection out to its own collection rather than unnecessarily nest it in its own collection.

    // Important: These actions are asynchronous. Handle errors appropriately.
    
    // store user's private information (e.g. username, email, phone, address)
    db.collection("users").document(mUser.uid).set(privateUserInfo)
    
    // store user's public profile information (e.g. username, display name, country)
    db.collection("profiles").document(mUser.uid).set(publicUserInfo)
    

    Then in your interface, you can use /profiles for your query and to also help users search each other.

    db.collection("profiles").get()
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search