Search code examples
flutterfirebasegoogle-cloud-platformgoogle-cloud-firestoretransactions

How to delete atomically from two firestore collections in flutter


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();
    }

Solution

  • (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.