How to return failed task result in continuation task [duplicate]

霸气de小男生 提交于 2021-01-29 15:06:27

问题


I'm writing my first app in Kotlin and am using Firestore & Firebase Storage. In the process of deleting a document, I want to delete all files in Storage that the document references (as it is the only reference to them in my case). If the Storage delete fails, I want to abort the document delete, in order to avoid orphan files in my Storage. I also want to do everything in "one Task", to allow showing a progress bar properly. My simplified code looks like this:

    fun deleteItem(id: String): Task<Void>? {
        val deleteTask = deleteTaleMedia(id)
        continueWithTaskOrInNew(deleteTask) { task ->
            if (task?.isSuccessful != false) { ... }
        }
    }

    fun deleteItemMedia(id: String): Task<Void>? =
        getItem(id)?.continueWithTask { task ->
            if (task.isSuccessful)
                task.result?.toObject(ItemModel::class.java)?.let { deleteFiles(it.media) }
            else ???
        }

    fun deleteFiles(filesList: List<String>): Task<Void>? {
        var deleteTask: Task<Void>? = null
        for (file in filesList) deleteTask = continueWithTaskOrInNew(deleteTask) { task ->
            if (task?.isSuccessful != false) deleteFile(file)
            else task 
        }
        return task
    }

    fun deleteFile(fileName: String) = Firebase.storage.getReferenceFromUrl(fileName).delete()

    fun getItem(id: String): Task<DocumentSnapshot>? {
        val docRef = userDocRef?.collection(COLLECTION_PATH)?.document(id)
        return docRef?.get()
            ?.addOnCompleteListener { ... }
    }

    fun <ResultT, TContinuationResult> continueWithTaskOrInNew(
        task: Task<ResultT>?,
        continuation: (Task<ResultT>?) -> Task<TContinuationResult>?
    ) = task?.continueWithTask { continuation.invoke(task) } ?: continuation.invoke(null)

data class ItemModel(
    @DocumentId val id: String = "",
    var title: String = "",
    var media: List<String> = listOf()
)

My problem comes in deleteItemMedia function (the "???" at the end). In case the get task failed, I want to return a task that will tell my deleteItem function to abort deletion (task.isSuccessful == false). I cannot return the get task itself (replace "???" with "task" in code), because it's type (Task<DocumentSnapshot>) differs from the type of the delete task (Task<Void>). I cannot return null, as null is returned in the case of no media at all, which is a valid case for me (document should be deleted). Is there a way to create a new "failed Task"?


回答1:


In the process of deleting a document I want to delete all files in Storage that the document references (as it is the only reference to them in my case).

There is no API that's doing that. You have to perform both delete operations yourself.

I also want to do everything in "one Task", to allow showing a progress bar properly.

Unfortunately, this is not possible in a single go. If you think of an atomic operation, this also not possible because none of the Firebase services support this kind of cross-product transactional operations. What you need to do is, get the document, get the references to the files in the Storage, delete the document and as soon as the delete operation is complete, delete the files. You can definitely reduce the risk by trying to roll-back the data from the client, but you cannot do them atomic, in "one Task". However, at some point in time, there will be an Exception that the client can't rollback.

If the Storage delete fails, I want to abort the document delete, in order to avoid orphan files in my Storage.

To avoid that, first, try not to have incomplete data. For instance, when you read the document and you get the corresponding Storage URLs, don't blindly assume that all those files actually exist. A file can unavailable for many reasons (was previously deleted, for some reasons the service is unavailable, etc.)

Another approach might be to use Cloud Functions for Firebase, so you can delete the desired document, and use onDelete function to delete the corresponding files from the Storage. Meaning, when document delete fails, the files from the Storage won't be deleted. If the operation to delete the document is successful, the Cloud Function will be triggered and the images will be deleted from the Storage. This approach will drastically reduce the chances of having failures between the document delete operation and the deletion of the files from Storage, but it doesn't eliminate that chance 100%.

Besides that, the most common approach to avoid failures is to make your code as robust as you possibly can against failure and do frequent database cleanups.



来源:https://stackoverflow.com/questions/65744647/how-to-return-failed-task-result-in-continuation-task

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!