How to add subcollection to a document in Firebase Cloud Firestore

JavaAndroidIosFirebaseGoogle Cloud-Firestore

Java Problem Overview


The documentation does not have any examples on how to add a subcollection to a document. I know how to add document to a collection and how to add data to a document, but how do I add a collection (subcollection) to a document?

Shouldn't there be some method like this:

dbRef.document("example").addCollection("subCollection")

Java Solutions


Solution 1 - Java

Edit 13 Jan 2021:

According to the updated documentation regarding array membership, now it is possible to filter data based on array values using whereArrayContains() method. A simple example would be:

CollectionReference citiesRef = db.collection("cities");
citiesRef.whereArrayContains("regions", "west_coast");

> This query returns every city document where the regions field is an array that contains west_coast. If the array has multiple instances of the value you query on, the document is included in the results only once.


Assuming we have a chat application that has a database structure that looks similar to this:

enter image description here

To write a subCollection in a document, please use the following code:

DocumentReference messageRef = db
    .collection("rooms").document("roomA")
    .collection("messages").document("message1");

If you want to create messages collection and call addDocument() 1000 times will be expensive for sure, but this is how Firestore works. You can switch to Firebase Realtime Database if you want, where the number of writes doesn't matter. But regarding Supported Data Types in Firestore, in fact, you can use an array because is supported. In Firebase Realtime database you could also use an array, but this is which is an anti-pattern. One of the many reasons Firebase recommends against using arrays is that it makes the security rules impossible to write.

Cloud Firestore can store arrays, it does not support querying array members or updating single array elements. However, you can still model this kind of data by leveraging the other capabilities of the Cloud Firestore. Here is the documentation where is very well explained.

You also cannot create a subcollection with 1000 messages and add all of them to the database and at the same time to consider a single record. It will be considered one write operation for every message. In total 1000 operations. The picture above does not show how to retrieve data, it shows a database structure in which you have something like this:

collection -> document -> subCollection -> document

Solution 2 - Java

Here's a variation where the subcollection is storing ID values at the collection level, rather than within a document where the subcollection is a field there with additional data.

This is useful for connecting a 1-to-Many ID mapping w/out having to drill through an additional document:

function fireAddStudentToClassroom(studentUserId, classroomId) {

    var db = firebase.firestore();
    var studentsClassroomRef =
        db.collection('student_class').doc(classroomId)
          .collection('students');

    studentsClassroomRef
        .doc(studentUserId)
        .set({})
        .then(function () {
            console.log('Document Added ');
        })
        .catch(function (error) {
            console.error('Error adding document: ', error);
        });
}

Thanks to [@Alex's answer][1] [1]: https://stackoverflow.com/a/47517402/2162226

> This answer a bit off from the original question here, where it explicitly asks for adding a collection to a document. However, after searching for a solution for this scenario and not finding any mention in docs or on SO, this post seems like a reasonable place to share the findings

Solution 3 - Java

Here's my code:

firebase.firestore().collection($scope.longLanguage + 'Words').doc($scope.word).set(wordData)
  .then(function() {
    console.log("Collection added to Firestore!");
    var promises = [];
    promises.push(firebase.firestore().collection($scope.longLanguage + 'Words').doc($scope.word).collection('AudioSources').doc($scope.accentDialect).set(accentDialectObject));
    promises.push(firebase.firestore().collection($scope.longLanguage + 'Words').doc($scope.word).collection('FunFacts').doc($scope.longLanguage).set(funFactObject));
    promises.push(firebase.firestore().collection($scope.longLanguage + 'Words').doc($scope.word).collection('Translations').doc($scope.translationLongLanguage).set(translationObject));
    Promise.all(promises).then(function() {
      console.log("All subcollections were added!");
    })
    .catch(function(error){
      console.log("Error adding subcollections to Firestore: " + error);
    });
  })
  .catch(function(error){
    console.log("Error adding document to Firestore: " + error);
  });

This makes a collection EnglishWords, which has a document of. The document of has three subcollections: AudioSources (recordings of the word in American and British accents), FunFacts, and Translations. The subcollection Translations has one document: Spanish. The Spanish document has three key-value pairs, telling you that 'de' is the Spanish translation of 'of'.

The first line of the code creates the collection EnglishWords. We wait for the promise to resolve with .then, and then we create the three subcollections. Promise.all tells us when all three subcollections are set.

IMHO, I use arrays in Firestore when the entire array is uploaded and downloaded together, i.e., I don't need to access individual elements. For example, an array of the letters of the word 'of' would be ['o', 'f']. The user can ask, "How do I spell 'of'?" The user isn't going to ask, "What's the second letter in 'of'?"

I use collections when I need to access individual elements, a.k.a. documents. With the older Firebase Realtime Database, I had to download arrays and then iterate through the arrays with forEach to get the element I wanted. This was a lot of code, and with a deep data structure and/or large arrays I was downloading tons of data that I didn't need, and slowing my app running forEach loops on large arrays. Firestore puts the iterators in the database, on their end, so that I can request a single element and it sends me just that element, saving me bandwidth and making my app run faster. This might not matter for a web app, if your computer has a broadband connection, but for mobile apps with poor data connections and slow devices this is important.

Here are two pictures of my Firestore: enter image description here

enter image description here

Solution 4 - Java

From the docs:

>You do not need to "create" or "delete" collections. After you create the first document in a collection, the collection exists. If you delete all of the documents in a collection, it no longer exists.

Solution 5 - Java

too late for an answer but here is what worked for me,

        mFirebaseDatabaseReference?.collection("conversations")?.add(Conversation("User1"))
            ?.addOnSuccessListener { documentReference ->
                Log.d(TAG, "DocumentSnapshot written with ID: " + documentReference.id)
                mFirebaseDatabaseReference?.collection("conversations")?.document(documentReference.id)?.collection("messages")?.add(Message(edtMessage?.text.toString()))
            }?.addOnFailureListener { e ->
                Log.w(TAG, "Error adding document", e)
            }

add success listener for adding document and use firebase generated ID for a path. Use this ID for the complete path for a new collection you want to add. I.E. - dbReference.collection('yourCollectionName').document(firebaseGeneratedID).collection('yourCollectionName').add(yourDocumentPOJO/Object)

Solution 6 - Java

Here i faced the same issue and solve with the answere of @Thomas David Kehoe

db.collection("First collection Name").doc("Id of the document").collection("Nested collection Name").add({
                //your data
            }).then((data) => {
                console.log(data.id);
                console.log("Document has added")
            }).catch((err) => {
                console.log(err)
            })

Solution 7 - Java

Okay so I recently faced a similar problem given the recent update in the firebase/firestore documentation.

And here is a solution that worked for me

  const sendMessage = async () => {
    await setDoc(doc(db, COLLECTION_NAME, projectId, SUB_COLLECTION_NAME, nanoid()), {
      text:'this is a sample text',
      createdAt: serverTimestamp(),
      name: currentUser?.firstName + ' ' + currentUser?.lastName,
      photoUrl: currentUser?.photoUrl,
      userId: currentUser?.id,
    });
  }

You can find a similar example in the docs https://firebase.google.com/docs/firestore/data-model#web-version-9_3

chat room

If you wish to listen for live update you can use a similar method as follows

const messagesRef = collection(db, COLLECTION_NAME, projectId, SUB_COLLECTION_NAME)
      
const liveUpdate = async () => {
        const queryObj = query(messagesRef, orderBy("createdAt"), limit(25));
        onSnapshot(queryObj, (querySnapshot) => {
          const msgArr: any = [];
          querySnapshot.forEach((doc) => {
            msgArr.push({ id: doc.id, ...doc.data() })
          });
          console.log(msgArr);
        });
      }

Solution 8 - Java

There is no separate method to add sub-collection into the document. You can just call the collection method itself. If the collection exists it will reference that otherwise create a new one.

dbRef.document("example").collection("subCollection")

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionrgoncalvView Question on Stackoverflow
Solution 1 - JavaAlex MamoView Answer on Stackoverflow
Solution 2 - JavaGene BoView Answer on Stackoverflow
Solution 3 - JavaThomas David KehoeView Answer on Stackoverflow
Solution 4 - JavaChristian_MView Answer on Stackoverflow
Solution 5 - JavaMihir PatelView Answer on Stackoverflow
Solution 6 - JavaMubasher AliView Answer on Stackoverflow
Solution 7 - JavaChinedu OguejioforView Answer on Stackoverflow
Solution 8 - JavaKunal SinghalView Answer on Stackoverflow