Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Modify the Deferred result

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?

like image 755
Nikolay Kulachenko Avatar asked Apr 10 '18 20:04

Nikolay Kulachenko


1 Answers

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.

like image 116
Roman Elizarov Avatar answered Nov 10 '22 19:11

Roman Elizarov