23

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

Sergio
  • 27,326
  • 8
  • 128
  • 149
Dmytro Rostopira
  • 10,588
  • 4
  • 64
  • 86

2 Answers2

36

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.

Roman Elizarov
  • 27,053
  • 12
  • 64
  • 60
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'
Sergio
  • 27,326
  • 8
  • 128
  • 149