Search code examples
javakotlinkotlinx.coroutines

Coroutines delegate exceptions


Currently, I have some scenario like this where I have java interface callback which looks something like this.

Java Callback

interface Callback<T> {
    void onComplete(T result)

    void onException(HttpResponse response, Exception ex)
}

Suspending function for the above look like this

suspend inline fun <T> awaitCallback(crossinline block: (Callback<T>) -> Unit) : T =
     suspendCancellableCoroutine { cont ->
        block(object : Callback<T> {
            override fun onComplete(result: T) = cont.resume(result)
            override fun onException(e: Exception?) {
                e?.let { cont.resumeWithException(it) }
            }
        })
    }

My calling function looks like this

fun getMovies(callback: Callback<Movie>) {
    launch(UI) {
        awaitCallback<Movie> {
            // I want to delegate exceptions here.
            fetchMovies(it)
        }
    }

What I'm currently doing to catch exception is this

fun getMovies(callback: CallbackWrapper<Movie>) {
    launch(UI) {
        try{
            val data = awaitCallback<Movie> {
                // I want to delegate exceptions here.
                fetchMovies(it)
            }
            callback.onComplete(data)
        }catch(ex: Exception) {
            callback.onFailure(ex)
        } 
    }
}

// I have to make a wrapper kotlin callback interface for achieving the above

interface CallbackWrapper<T> {
    fun onComplete(result: T) 

    fun onFailure(ex: Exception)
}

Questions

  1. The above works but is there any better way to do this?? One of the main thing is I'm currently migrating this code from callback so I have ~20 api calls and I don't want to add try/catch everywhere to delegate the result along with the exception.

  2. Also, I'm only able to get exception from my suspending function is there any way to get both HttpResponse as well as the exception. Or is it possible to use existing JAVA interface.

  3. Is there any better way to delegate the result from getMovies without using callback??


Solution

  • Is there any better way to delegate the result from getMovies without using callback?

    Let me start with some assumptions:

    • you're using some async HTTP client library. It has some methods to send requests, for example httpGet and httpPost. They take callbacks.

    • you have ~20 methods like fetchMovies that send HTTP requests.

    I propose to create an extension suspend fun for each HTTP client method that sends a request. For example, this turns an async client.httpGet() into a suspending client.awaitGet():

    suspend fun <T> HttpClient.awaitGet(url: String) =
        suspendCancellableCoroutine<T> { cont ->
            httpGet(url, object : HttpCallback<T> {
                override fun onComplete(result: T) = cont.resume(result)
    
                override fun onException(response: HttpResponse?, e: Exception?) {
                    e?.also {
                        cont.resumeWithException(it)
                    } ?: run {
                        cont.resumeWithException(HttpException(
                                "${response!!.statusCode()}: ${response.message()}"
                        ))
                    }
                }
            })
        }
    

    Based on this you can write suspend fun fetchMovies() or any other:

    suspend fun fetchMovies(): List<Movie> = 
            client.awaitGet("http://example.org/movies")
    

    My reduced example is missing the parsing logic that turns the HTTP response into Movie objects, but I don't think this affects the approach.

    I'm currently migrating this code from callback so I have ~20 api calls and I don't want to add try/catch everywhere to delegate the result along with the exception.

    You don't need a try-catch around each individual call. Organize your code so you just let the exception propagate upwards to the caller and have a central place where you handle exceptions. If you can't do that, it means you've got a specific way to handle each exception; then the try-catch is the best and idiomatic option. It's what you would write if you had a plain blocking API. Especially note how trivial it is to wrap many HTTP calls in a single try-catch, something you can't replicate with callbacks.

    I'm only able to get exception from my suspending function is there any way to get both HttpResponse as well as the exception.

    This is probably not what you need. What exactly do you plan to do with the response, knowing that it's an error response? In the example above I wrote some standard logic that creates an exception from the response. If you have to, you can catch that exception and provide custom logic at the call site.