Correct way to suspend coroutine until Task<T> is complete
Asked Answered
N

2

24

I've recently dove into Kotlin coroutines Since I use a lot of Google's libraries, most of the jobs is done inside Task class

Currently I'm using this extension to suspend coroutine

suspend fun <T> awaitTask(task: Task<T>): T = suspendCoroutine { continuation ->
    task.addOnCompleteListener { task ->
        if (task.isSuccessful) {
            continuation.resume(task.result)
        } else {
            continuation.resumeWithException(task.exception!!)
        }
    }
}

But recently I've seen usage like this

suspend fun <T> awaitTask(task: Task<T>): T = suspendCoroutine { continuation ->
    try {
        val result = Tasks.await(task)
        continuation.resume(result)
    } catch (e: Exception) {
        continuation.resumeWithException(e)
    }
}

Is there any difference, and which one is correct?

UPD: second example isn't working, idk why

Neilson answered 13/5, 2018 at 11:49 Comment(0)
C
39

The block of code passed to suspendCoroutine { ... } should not block a thread that it is being invoked on, allowing the coroutine to be suspended. This way, the actual thread can be used for other tasks. This is a key feature that allows Kotlin coroutines to scale and to run multiple coroutines even on the single UI thread.

The first example does it correctly, because it invokes task.addOnCompleteListener (see docs) (which just adds a listener and returns immediately. That is why the first one works properly.

The second example uses Tasks.await(task) (see docs) which blocks the thread that it is being invoked on and does not return until the task is complete, so it does not allow coroutine to be properly suspended.

Chaplin answered 14/5, 2018 at 11:50 Comment(0)
M
1

One of the ways to wait for a Task to complete using Kotlin Coroutines is to convert the Task object into a Deferred object by applying Task.asDeferred extension function. For example for fetching data from Firebase Database it can look like the following:

suspend fun makeRequest() {
    val task: Task<DataSnapshot> = FirebaseDatabase.getInstance().reference.get()
    val deferred: Deferred<DataSnapshot> = task.asDeferred()
    val data: Iterable<DataSnapshot> = deferred.await().children

    // ... use data
}

Dependency for Task.asDeferred():

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.5.2'

To call suspend function we need to launch a coroutine:

someCoroutineScope.launch {
    makeRequest()
}

someCoroutineScope is a CoroutineScope instance. In android it can be viewModelScope in ViewModel class and lifecycleScope in Activity or Fragment, or some custom CoroutineScope instance. Dependencies:

implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0'
Mahdi answered 22/1, 2022 at 16:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.