Network call interface in Kotlin

Network call interface in Kotlin

When starting with Kotlin on Android, the first thing I needed to do was to fetch data over the internet. I knew that coroutines provide a built-in way for asynchronous work (no need for AsyncTask or RxJava!), but how do I define my network call interfaces?

The wrong way: Async-style functions

You might think that you need something that you can call from anywhere and just doesn't block the main thread. Googling and StackOverflowing around, you might end up with this easy (but wrong) solution.

fun callServiceAsync(): Deferred<MyResult> = GlobalScope.async(Dispatchers.IO) {
    // Make your blocking network call here (e.g. with OkHttp)
}
Network call API
GlobalScope.launch {
    val result = callServiceAsync().await()
    // Do something with it
}
Main app

Awesome, right? You use Dispatchers.IO so you are not blocking the main thread. And you can call this from anywhere.

Unfortunately, using GlocalScrope is an anti-pattern in Kotlin and not recommended for various reasons (e.g. not bound to any job and will continue running even if not needed).

The ideal way is to create coroutines and use them inside ContextScopes that are bounded to the lifecycle of a "parent" element (e.g. your ViewModel). With this approach, the active coroutines will be cancelled when they are not needed anymore (e.g. when the user navigates to another screen). Also, you make sure that you don't have common memory leaks (e.g. network calls that hanged and no one stopped them) in your app.

suspend fun callService(): MyResult = withContext(Dispatchers.IO) {
    // Make your blocking network call here (e.g. with OkHttp)
}
Network call API
viewModelScope.launch {
    val result = callService()
    // Do something with it
}
Main app

A common use case is to make multiple network calls before processing/combining your results to something the user can see. If you just call suspend functions one after the other they will be executed sequentially.

viewModelScope.launch {
    val result1 = callService1()
    val result2 = callService2()
    // Combine/process the 2 results
}
This will run sequentially (i.e. the callService1() will finish, then callService2() will start) 

To run them in parallel, use the async coroutine builder. Execution starts as soon as the async block is defined.

viewModelScope.launch {
    val result1 = async { callService1() }
    val result2 = async { callService2() }
    result1.await()
    result2.await()
    // Combine/process the 2 results
}
The network calls will run in parallel (i.e. callService1() and callService2() will start at the same time)

The experimental way: Flows

If you need to do complicated compositions of the results, returning the result as a Flow might be suitable. Flows allow you the flexibility to compose and transform the return results in a fluent way.

Didn't try it yet in a project of mine, but seems like a promising approach.

fun callService(): Flow<MyResult> = flow { 
    // Make your blocking network call here (e.g. with OkHttp)
}.flowOn(Dispatchers.IO)
Network call API
viewModelScope.launch {
    callService().transform {
        // Do stuff with the response
    }
    .zip(callService2())
    .collect {
        // Combine/process the 2 results
    }
}
Main app

Happy network calling!

Show Comments