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