I want to implement this code synchronously
but job.join
, deferred.await
, and firebase await
, not working.
Does anyone know a solution?
CoroutineScope(Dispatchers.Main).launch {
val job = launch {
Tasks.whenAllComplete(tasks)
.addOnCompleteListener {
Log.d("user-get", "on-0-> addOnComplete")
tasks.forEach { task ->
val snapshot = task.result
Log.d("user-get", "on->" task.toString())
snapshot.documents.forEach {
docs.add(it)
}
}
}
}
job.join()
Log.d("user-get", "job-end")
}
Log order:
D/user-get: job-end
D/user-get: on-0-> addOnComplete
D/user-get: on->com.google.android.gms.tasks.zzw@5c0519d
deferred
and await
, and Task.whenAllComplete(tasks).addOnCompleteListener { ~~~ }.await
are also same log result.
CodePudding user response:
I think what you need to do is to not use addOnCompleteListener
and use await()
function on the Task
object instead:
coroutineScope.launch {
val tasks = Tasks.whenAllComplete(tasks).await() // Awaits the completion of the task without blocking a thread.
tasks.forEach { task ->
val snapshot = task.result
Log.d("user-get", "on->" task.toString())
snapshot.documents.forEach {
docs.add(it)
}
}
Log.d("user-get", "job-end")
}
CodePudding user response:
As I commented in an earlier question of yours, there is no need to launch a coroutine when you attach a listener. It's one, or the other. When you call Tasks#whenAllComplete(Task...<?> tasks
), the type of object that is returned is Task<List<Task<?>>
. This means that all the heavy work is done behind the scenes, in a different thread. So there is nothing that can block the main thread. When you attach a listener, it means that you'll wait until the operation is complete, or fails with an exception. So you either get the request out of the coroutine or, as @Sergio mentioned in his answer, remove the listener and call await(). Please also note that this function is a suspend function and not a blocking function.
Now, since this operation can raise an exception, is recommended to add the following lines of code inside a method, perhaps in a repository class, and use a try-catch together with Kotlin flow:
fun getDocs() = flow {
try {
val tasks = Tasks.whenAllComplete(tasks).await()
tasks.forEach { task ->
val snapshot = task.result
snapshot.documents.forEach {
docs.add(it)
}
}
emit(docs)
} catch (e: Exception) {
emit(e)
}
}
And inside a ViewModel class launch a coroutine and collect the result:
fun getDocs() = viewModelScope.launch {
repo.getDocs().collect { docs ->
//Do what you need to do with the documents
}
}
So most likely you might consider converting the list of DocumentSnapshot objects into a list of custom objects of yours. That being said, an even simpler solution, would be not to iterate but to call QuerySnapshot#toObjects(Class clazz):
val tasks = Tasks.whenAllComplete(tasks).await()
val docs = tasks.toObjects(YourModelClass::class.java)
I have written an article called:
Where I explain step by step how you can achieve that. Here is the corresponding repo. And here is a line of code where I do the the conversion.