I've got a list of files to upload to Firebase storage from my Kotlin Android app. I'm finding that if I upload them asynchronously, there can be so many uploading at once that my Android app sometimes crashes with an Out Of Memory error.
So I'm trying to make the uploads sequential, with just one uploading at a time, and the loop pausing to wait for the upload to complete before continuing.
I see that I can use something like fileRef.putFile(fileUri).await().storage.downloadUrl.await().toString()
to wait for Firebase to return the Url of a successful upload, before continuing with the loop. When the upload returns a URL, my code works fine.
But if there is an error in the upload, that line of code gets stuck there, waiting indefinitely for that URL.
So how do I wait for successful downloads, but also catch errors and cancel the wait if there is an error?
Is await() even the correct approach?
Here is the code (redacted for simplicity) I'm trying to convert into uploading files sequentially rather than simultaneously. It works fine except rarely when too many simultaneous uploads throw an OOM:
val fileUri = Uri.fromFile(file)
val fileRef =
Firebase.storage.reference.child("$BLAH/$userID/${Uuid}/${fileUri.lastPathSegment}")
fileRef.putFile(fileUri).addOnSuccessListener {
fileRef.downloadUrl.addOnSuccessListener {
Firebase.firestore.collection(BLAH).document(BLAH)
.set(
blinkybill.toCloud(BLAH, it.toString()),
SetOptions.merge()
)
.addOnSuccessListener {
updateStatus(CloudSyncStatus.STARTED)
CoroutineScope(Dispatchers.IO).launch {
blahDao.updateBlah(
blahEntity.copy(
fileUploaded = true
)
)
}
}
.addOnFailureListener { e ->
logger("Error uploading the URL to Firebase. Not the one I care about right now")
updateStatus(CloudSyncStatus.ERROR)
}
}
}.addOnFailureListener {
logger("THIS IS THE UPLOAD ERROR I WANT TO CATCH WHILE WAITING FOR DOWNLOAD")
updateStatus(CloudSyncStatus.ERROR)
}
As I see in your code, you're using a FailureListener
to log the error, which is fine. However, if you want to use Kotlin Coroutines and await for the operations to finish using:
fileRef.putFile(fileUri).await().storage.downloadUrl.await().toString()
Then to know when one of the operations fails, you have to add this line of code inside a try-catch block:
try {
fileRef.putFile(fileUri).await().storage.downloadUrl.await().toString()
} catch (e: Exception) {
logger("THIS IS THE UPLOAD ERROR I WANT TO CATCH WHILE WAITING FOR DOWNLOAD")
updateStatus(CloudSyncStatus.ERROR)
}
But please notice, that you only log the fact that you get an error but aren't getting the actual error message that indicates the exact failure, so preferably you should log the error message like this:
Log.d("TAG", "${e.message}")