Home > Software engineering >  FireStore Tasks.whenAllComplete with Coroutines
FireStore Tasks.whenAllComplete with Coroutines

Time:09-09

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.

  • Related