Wait for result from multiple callbacks/lambdas in Kotlin

Viewed 3057

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.

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.

Related