Given an API (implemented by Retrofit) that returns a model. I wrap an old fashioned Call into a Deferred
using the extention function:
fun <T> Call<T>.toDeferred(): Deferred<T> {
val deferred = CompletableDeferred<T>()
// cancel request as well
deferred.invokeOnCompletion {
if (deferred.isCancelled) {
cancel()
}
}
enqueue(object : Callback<T> {
override fun onFailure(call: Call<T>?, t: Throwable) {
deferred.completeExceptionally(t)
}
override fun onResponse(call: Call<T>?, response: Response<T>) {
if (response.isSuccessful) {
deferred.complete(response.body()!!)
} else {
deferred.completeExceptionally(HttpException(response))
}
}
})
return deferred
}
Now I can get my model like this:
data class Dummy(val name: String, val age: Int)
fun getDummy(): Deferred<Dummy> = api.getDummy().toDeferred()
But how can I modify the object inside the Deferred
and return a Deferred
:
fun getDummyAge(): Deferred<Int> {
// return getDummy().age
}
I'm new to coroutines so may be this is not how the things get done here. Assuming I'm an RxJava fan I'd implement this case like:
fun getDummy(): Single<Dummy> = api.getDummy().toSingle()
fun getDummyAge(): Single<Int> = getDummy().map { it.age }
So should I try to return a Deferred
from the getDummyAge
function? Or may be it's better to declare a suspended fun
whenever possible and call deferred.await()
on all my api's methods?
If you follow async style of programming, that is writing functions that return Deferred<T>
, then you can define async function getDummyAge
like this:
fun getDummyAge(): Deferred<Int> = async { getDummy().await().age }
However, this style of programming is generally not recommend in Kotlin. The idiomatic Kotlin approach is to define a suspending extension function Call<T>.await()
with the following signature:
suspend fun <T> Call<T>.await(): T = ... // more on it later
and use it to write suspending function getDummy
that returns a result of Dummy
type directly without wrapping it into a deferred:
suspend fun getDummy(): Dummy = api.getDummy().await()
In this case you can trivially write suspending function getDummyAge
:
suspend fun getDummyAge(): Int = getDummy().age
For Retrofit calls you can implement await
extension like this:
suspend fun <T> Call<T>.await(): T = suspendCancellableCoroutine { cont ->
cont.invokeOnCompletion { cancel() }
enqueue(object : Callback<T> {
override fun onFailure(call: Call<T>?, t: Throwable) {
cont.resumeWithException(t)
}
override fun onResponse(call: Call<T>?, response: Response<T>) {
if (response.isSuccessful) {
cont.resume(response.body()!!)
} else {
cont.resumeWithException(HttpException(response))
}
}
})
}
If you want to learn more on the differences in style between async and suspending functions, then I'd suggest to watch Introduction to Coroutines from KotlinConf 2017. If you prefer a short read, then this section from the design document offers some insight, too.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With