skip to Main Content

I have two collections: users and ads in Firestore.

When I delete a user file the users collection I also need to delete all his ads for the ads collection.

Is the bulk version correct?


  Future<void> storeMarkAsDeleted() async {
    final fs = FirebaseFirestore.instance;
    final writeBatch = fs.batch();

    // = Open a transaction to perform both operations
    final user = await fs.collection(collectionName).doc(id));

      /// Reads the ad document
      final ads = await fs
          .collection(collectionName)
          .where("ownerId", isEqualTo: id)
          .get();

      // Mark user as deleted
      writeBatch.update(user.reference, {
        "deleted": true,
      });

      // Update all docs
      for (final doc in ads.docs) {
     
        writeBatch.update(doc.reference, {
          "clearedForSale": false,
          "deleted": true,
        });
      }
      // unless commit is called, nothing happens.
      writeBatch.commit();
    }

2

Answers


  1. If it works the way you expect, then it’s likely correct. However, you should be aware that there is a short period of time between the query for ads docs and their deletion where a new doc could be added to the collection and will not be marked as deleted when the batch is committed.

    Login or Signup to reply.
  2. (I also saw your other question Does it makes sense to batch update within a transaction in Flutter with Firebase? about atomically executing the deletions within a Transaction).

    One possibility is to use a Transaction in a Cloud Function. While you cannot execute a Query in a Transaction executed via a Client SDK (included Firebase Flutter plugins) you can do it with the server client libraries (C#, Go, Java, Node.js, PHP, Python, Ruby) and therefore within a Cloud Function.

    This page in the documentation explains the reasons for this difference.

    In your Cloud Function, you first delete the user document, then you execute a query that returns the ads documents corresponding to the user and you delete all of them by looping on the QuerySnapshot. The best is to use a Callable Cloud Function that you call from your Flutter app by passing the user uid.

    Something along the following lines for Node.js Cloud Functions 2nd Generation :

    const { initializeApp } = require("firebase-admin/app");
    const { onCall, HttpsError } = require("firebase-functions/v2/https"); 
    const { getFirestore } = require("firebase-admin/firestore");
    
    initializeApp();
    
    
    exports.deleteUserDocs = onCall(async (request) => {
      const userId = request.data.userId;
    
      await getFirestore().runTransaction(async (transaction) => {
        const userDocRef = getFirestore().collection("users").doc(userId);
        const queryRef = getFirestore()
          .collection("ads")
          .where("ownerId", "==", userId);
        const userAdsSnapshots = await transaction.get(queryRef);
    
        transaction.delete(userDocRef);
        userAdsSnapshots.forEach((userAdSnap) => {
          transaction.delete(userAdSnap.ref);
        });
        return Promise.resolve();
      });
    
      return {result: "success"}
    });
    

    I let you add try/catch block and manage the potential errors as explained in the doc.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search