Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wait for result from multiple callbacks/lambdas in Kotlin

I'm making an app in Kotlin. Up until this point, my networking calls didn't have to be used together. I am now in a spot where I need to make two concurrent networking calls, pause until I receive both of their responses, and then continue execution. I'm trying to accomplish something like this:

    //first networking call, get resourceOne
    var resourceOne : String?
    Server.asyncRequest(RequestBuilder(endpoints.second, ids, params)) { resource: String?, error: ServiceError? ->
        resourceOne = resource
    }

    //second networking call, get resourceTwo
    var resourceTwo : String?
    Server.asyncRequest(RequestBuilder(endpoints.third, ids, params)) { resource: String?, error: ServiceError? ->
        resourceTwo = resource
    }

    //do something here wiith resourceOne and resourceTwo

The function header for my asyncRequest function is:

fun asyncRequest(requestBuilder: RequestBuilder, completion: (resource: String?, error: ServiceError?) -> Unit) {

It just wraps around an okhttp request and does some extra processing/parsing. Normally I would just take the result (resource) and process it inside of the completion lambda, but since I need both values, I can't do that here. I've tried doing something similar to this but my asyncRequest function does not have a return type, so I have no way of doing the async/await the way the link does.

like image 524
BillyDean Avatar asked Mar 05 '20 18:03

BillyDean


People also ask

What are Lambdas in Kotlin?

// Lambdas are code blocks enclosed in curly braces. Kotlin uses function types, such as (Int) -> String, for declarations that deal with functions: val onClick: () -> Unit = .... These types have a special notation that corresponds to the signatures of the functions - their parameters and return values:

What is async function in Kotlin?

Kotlin’s async function allows running concurrent coroutines and returns a Deferred<T> result. Deferred is a non-blocking cancellable future to act as a proxy for a result that is initially unknown. For example, by calling Deferred#wait method, we wait until the task is done, and then we have the result.

How do you declare functions in Kotlin?

Kotlin uses function types, such as (Int) -> String, for declarations that deal with functions: val onClick: () -> Unit = .... These types have a special notation that corresponds to the signatures of the functions - their parameters and return values:

How do you achieve concurrency in Kotlin?

We’re going to use coroutines instead of threads as the promoted way to achieve concurrency in Kotlin. Kotlin’s async function allows running concurrent coroutines and returns a Deferred<T> result. Deferred is a non-blocking cancellable future to act as a proxy for a result that is initially unknown.


Video Answer


1 Answers

You can do it with Coroutines along with Flow with something like this:

Turn callbacks into suspendable functions with a suspendCancellableCoroutine {...} block:

suspend fun <T> request(requestBuilder: RequestBuilder): T = suspendCancellableCoroutine { cont ->
    Server.asyncRequest(requestBuilder) { resource: T, error: ServiceError? ->
        if(error != null)
            cont.resumeWithException(error) // Makes the Flow throw an exception
        else
            cont.resume(resource) // Makes the Flow emit a correct result
    }
}

Create a Flow to make the first request:

val resourceOneFlow = flow {
    emit(request<String>(RequestBuilder(endpoints.second, ids, params)))
}

Create a Flow to make the second request:

val resourceTwoFlow = flow {
    emit(request<String>(RequestBuilder(endpoints.third, ids, params)))
}

Combine both Flows with the zip operator:

val requestsResultFlow = resourceOneFlow.zip(resourceTwoFlow) { resourceOne, resourceTwo ->
    // Build whatever you need with resourceOne and resourceTwo here and let it flow
    "$resourceOne $resourceTwo".length // Here I concatenate both strings and return its length
}

Activate/Start the Flow with the collect operator and consume its result:

requestsResultFlow.collect { length ->
    // Consume the result here
    println("$length") // Here I print the number received
}

You have the Flow documentation here.

like image 57
Glenn Sandoval Avatar answered Oct 18 '22 23:10

Glenn Sandoval