Repository implementation where a suspend function of uploading and getting back the urls. the await() function said that it should be "suspend". so i added a Coroutine Scope block . inside the block have added urls to a list. the log statement inside the scope has required downloaded urls in the arraylist . but outside the block the list is empty. how to make the list updated outside the coroutine block and can anyone help me to understand how to work with Coroutine thanks
my code
override suspend fun addProductImagesToFirebaseStorage(
productImages: List<Uri>
): AddProductImagesResponse {
return try {
val downloadUrls = mutableListOf<Uri>()
val tasks = mutableListOf<UploadTask>()
productImages.forEach { downloadUrl ->
val task = categoryImageStorage.reference
.child("Home").child("PP")
.child("images")
.putFile(downloadUrl)
tasks.add(task)
}
Tasks.whenAllSuccess<UploadTask.TaskSnapshot>(tasks).addOnSuccessListener { uploadTask ->
uploadTask.forEach {
// downloadUrls.add(it.storage.downloadUrl.await()) Error: Suspension functions can be called only within coroutine body
CoroutineScope(Dispatchers.Default).launch {
downloadUrls.add(it.storage.downloadUrl.await())
Log.i(TAG,"Inside the block : $downloadUrls")
}
Log.i(TAG,"Outside the block : $downloadUrls")
}
}
Success(downloadUrls)
} catch(e:Exception) {
Failure(e)
}
}
CodePudding user response:
That's because the outside
log will be probably executed first, since changing the thread to Dispatchers.DEFAULT
takes more time than it takes JVM to execute the next line.
What I'm trying to say is, the coroutine
will start, immediately after that, the outside
log will print the list(which is empty), then the coroutine job will do it's work on another thread (Dispatchers.DEFAULT
), independently of the main program execution flow.
What I would suggest, is to use the MutableLiveData
or LiveData
so you can observe them to let the main program react when the list gets filled with downloadUrl
Here's more to LiveData and MutableLiveData
CodePudding user response:
As was stated by ndriqa, outside logging happens before the inside logging, since your coroutine will run in parallel and wait for download to complete before logging. During download, your code written outside of coroutine block continues to execute and your downloadUrls
remains empty until download completes. Therefore you get empty value in outside log.
Your goal is to make sure that your method addProductImagesToFirebaseStorage
return Success()
with downloaded value after download completes. Since your method is suspend
, it is able to wait for some long operation to complete (in your case download.await()
). However, you are calling await()
in the listener's method, therefore compiler does not know the context and cannot be sure that it is called from a coroutine or a suspend function.
Just call it like this to ensure that Success
will be created with up to date value
val tasks = mutableListOf<UploadTask>()
productImages.forEach { file -> // I renamed downloadUrl to file here to be clearer
val url = categoryImageStorage.reference // I also renamed task to url since the result will be not a task but a downloaded url
.child("Home").child("PP")
.child("images")
.putFile(file)
.await() // wait for it to be put
.storage
.downloadUrl
.await() // wait for it to download
downloadUrls.add(url)
}