Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How To Delete Firestore Collection From Android

Issue

I'm looking for a temporary solution to delete Collections from the client side for my proof of concept. I will eventually refactor this onto the server as recommended.

I'm adding the function to delete all of a particular Firestore user's account information including their saved Collections of content in the app. Per the Firestore documentation there is no prescribed approach to doing so from the client as it is recommended to handle this on the server.

like image 308
Adam Hurwitz Avatar asked Jan 28 '23 12:01

Adam Hurwitz


2 Answers

To delete an entire collection or subcollection from a Cloud Firestore database, you need to retrieve all the documents within the collection or subcollection and delete them.

If you have larger collections, you may want to delete the documents in smaller batches to avoid out-of-memory errors. So you should repeat the process until you've deleted the entire collection or subcollection.

Even if the delete operation is not recomended by Firebase team because it has negative security and performance implications, you can still do it but only for small collections. If you need to delete entire collections for web, do so only from a trusted server environment.

For Kotlin, please use the following functions:

private fun deleteCollection(collection: CollectionReference, executor: Executor) {
    Tasks.call(executor) {
        val batchSize = 10
        var query = collection.orderBy(FieldPath.documentId()).limit(batchSize.toLong())
        var deleted = deleteQueryBatch(query)

        while (deleted.size >= batchSize) {
            val last = deleted[deleted.size - 1]
            query = collection.orderBy(FieldPath.documentId()).startAfter(last.id).limit(batchSize.toLong())

            deleted = deleteQueryBatch(query)
        }

        null
    }
}

@WorkerThread
@Throws(Exception::class)
private fun deleteQueryBatch(query: Query): List<DocumentSnapshot> {
    val querySnapshot = Tasks.await(query.get())

    val batch = query.firestore.batch()
    for (snapshot in querySnapshot) {
        batch.delete(snapshot.reference)
    }
    Tasks.await(batch.commit())

    return querySnapshot.documents
}
like image 103
Alex Mamo Avatar answered Jan 31 '23 18:01

Alex Mamo


Updated Solution

The Firebase team's Delete Collections and Subcollections documented solution is more reliable and secure since it is implemented within a Cloud Function outside of the client. I've refactored my solution accordingly.

/**
* Initiate a recursive delete of documents at a given path.
*
* This delete is NOT an atomic operation and it's possible
* that it may fail after only deleting some documents.
*
* @param {string} data.path the document or collection path to delete.
*/

exports.deleteUser = () => functions.runWith({timeoutSeconds: 540, memory: '2GB'})
   .https.onCall((data, context) => {
    if (context.auth.uid !== data.userId)
      throw new functions.https.HttpsError(
        'permission-denied','Must be an administrative user to initiate delete.');
    const path = data.path;
    console.log(`User ${context.auth.uid} has requested to delete path ${path}`);

    return firebase_tools.firestore.delete(path, {
      project: process.env.GCLOUD_PROJECT,
      recursive: true,
      yes: true,
      token: functions.config().fb.token
    }).then(() => { return { path: path }; });
});

Old Solution (Performed on client side)

Passed into the method is the reference of the user's Collection and the batch size to process.

fun deleteCollection(collection: CollectionReference, batchSize: Int) {
    try {
        // Retrieve a small batch of documents to avoid out-of-memory errors/
        var deleted = 0
        collection
                .limit(batchSize.toLong())
                .get()
                .addOnCompleteListener {
                    for (document in it.result.documents) {
                        document.getReference().delete()
                        ++deleted
                    }
                    if (deleted >= batchSize) {
                        // retrieve and delete another batch
                        deleteCollection(collection, batchSize)
                    }
                }
    } catch (e: Exception) {
        System.err.println("Error deleting collection : " + e.message)
    }
}
like image 20
Adam Hurwitz Avatar answered Jan 31 '23 18:01

Adam Hurwitz