“Team leads review candidate’s whiteboard, binary-tree inversion solution.” Source

Making Android Networking Pretty with Kotlin Coroutines

Filip Babic
COBE

--

If you’re like me, you love abstracting everything away. Having reusable structures that reduce the amount of boilerplate is what makes you a good developer.

One such tedious task I’ve always wanted to abstract away is Networking. Huge callbacks, ugly error handling, parsing the values, and mapping parsed values into business logic models is all boilerplate I wanted to get rid of.

And I’ve actually managed to wrap it up in a nice API-like solution.

A better way to handle data

So each request has the same set of steps:

  1. We request the data and declare the type we’re expecting (some Model)
  2. JSON/XML arrives with its values, and we try to parse the response
  3. We check the Model, and if all required values are there, we display it, if not, we show an corresponding error

Being the same for all requests, it’s a great candidate for a generic solution (yup I love generics)!

Another thing we want to achieve is to have a single structure represent both a successful request, and an error case. A neat thing we could use is a Sealed class. In Java you’d usually try to find a library that does this type of things. However in Kotlin, I love trying to come up with a solution of mine. We’ll try to do this here, by creating a Result.

Result

Moving on to some actual implementation! We’ll use a sealed class to wrap up two response types : Success and Failure. They will have a value or an error as their properties, respectively.

sealed class Result<out T : Any>

data class Success<out T : Any>(val data: T) : Result<T>()

data class Failure(val error: Throwable?) : Result<Nothing>()

Only three lines of code??!! Yep, using Kotlin, we can pretty much make this follow KISS principle to the max. In case of a Success we get the Data (of type T), and in case of an Failure we get an error (or no error if it’s a user-made fallback).

Now we can check what we got from a request:

fun onResponse(result: Result<User>) = when (result) {
}

Quick note,when using a sealed class the compiler automatically offers us to exhaust all the cases. This means if we do get a Result, it can only be a Success or a Failure, nothing else.

Adding the “remaining branches”, and some implementation gives us:

fun onResponse(result: Result<User>) = when (result) {
is Success -> displayData(result.data)
is Failure -> showError(result.error)
}

This seems pretty straightforward. If we do get an error, we check the type and the code, depending on Throwable type, and in the happy path we display the data.

One more step is missing — the mapping. Often you’d have a Model declared for backend:

data class UserResponse(val id: String?,
val firstName: String?,
val lastName: String?,
val profileImage: String?)

Notice how all fields are nullable. It’s to mitigate unsuccessful parsing. I personally wouldn’t want to deal with nullable values all over my ViewModel, as, after all, we’re not writing Java to have to do all them nullchecks.

Which is why we use a different model just for business logic:

data class User(val id: String = "",
val firstName: String = "",
val lastName: String = "",
val profileImage: String = "")

All the values are non-null here, albeit being empty by default. You’re probably asking : ”You wouldn’t knowingly allow a user to pass through the app with an empty id, would you?

Nope. We wouldn’t. Which is why a mapping construct is needed between the process of receiving the parsed user and the business logic. Another great OOP idea comes to mind here, a Mappable interface!

Mappable<T>

interface Mappable<out T : Any> {
fun mapToResult(): Result<T>
}

Each Response model has to implement this. It’s the way of saying, alright, if I’m a UserResponse, my business name will be User to simplify things and differentiate a raw JSON response from an actual usable POKO.

Still, how do we know that a UserResponse really is valid?

By declaring a function/extension on each model to clarify when it’s okay to use it!

data class UserResponse(val id: String?,
val firstName: String?,
val lastName: String?,
val profileImage: String?) : Mappable<User> {

override fun mapToResult(): Result<User> = when {
isValid() -> Success(User(id ?: "",
firstName ?: "",
lastName ?: "",
profileImage ?: ""))

else -> Failure(Exception("User body is invalid"))
}

private fun isValid() = !id.isNullOrBlank() && !firstName.isNullOrBlank()
}

It just seems logical for the response to know whether or not it can map. It’s a bit ugly that we have to explicitly use an Elvis operator for values, but it’s a minor issue.

So now, when we receive the response we can pass the correct Result to the business layer. Way to go! :]

We’ve pretty much covered everything from the bucket list of ugly things in Android networking. One more task left to go — callbacks.

Async is hard to grasp

First of all let’s have an overview of async programming. My general opinion is that asynchronous programming is not something humans are made to understand. You call a function, and it runs, but somewhere in the future its result is being returned. Moreover the result is being returned by a Callback, something even more magical.

It’s one of the hardest concepts we have to explain to our interns here COBE Team.

First time I’ve encountered async programming was using an AsyncTask, which makes even less sense. You just implement its three base methods, and the data is passed around behind the curtains.

If only everything was sequential, you call a function, its result is computed, and you can use it right on the next line.

Enter Coroutines

Concurrency can cause headaches. Even more so if you’re trying to combine more than one request. Which is why Kotlin introduces a concept of coroutines into the JVM world.

Coroutines are very simple in their nature, but take a while to understand, as it’s hard to think of something that is both sequential and synchronous in a way, while working asynchronously. Have a look at this:

fun showUserProfile(userId: String) {
val user = service.getUser(userId)

view.showUserName(user.name)
}

Here we want to display the user’s name. It would be ideal if we could just request the user from a service, and immediately use it to display the data on the UI. Standard async approaches don’t allow us to do so, we need a lambda callback which will react to data arrival.

But by using coroutines, our code transfers quite a bit:

fun showUserProfile(userId: String) = async {
val user = async { service.getUser(userId) }.await()

view.showUserName(user.name)
}

Disclaimer: this is a loose implementation, behind the scenes it’s not as simple as this. Although once we implement our API, it will be even easier.

Async block here is a coroutine builder. It means that when everything inside it being run in a coroutine, it’s being suspended until a result comes back. Sounds familiar? The main difference this and asynchronous is that there isn’t an explicit callback we have to define.

In case we want to use a suspension function, or a coroutine somewhere, it must be used in a coroutine builder. So let’s implement a clever way of requesting data in a coroutine powered sequential block of code.

Hokus pokus

Magic behind the scenes

Our ideal flow has these steps:

  1. Shoot a request, receive back the Retrofit’s Call<T>
  2. Transform the aforementioned Call to a Coroutine/Deferred value
  3. Using the Mappable<T> interface map the data to a result case we need
  4. Return the value sequentially, so it’s ready to use on the next line

We’ve already prepared the parsing and mapping procedure, now we have to build a construct which will allow us to return a Deferred value rather than a Response. And the key selling point is: no callbacks (I might lie about this :]).

We should start by defining the base signature:

fun <T : Any> getResult(): Result<T> {

}

Currently, this doesn’t return anything, we must make it return a ready-baked value, but asynchronously! Let’s add a coroutine:

suspend fun <T : Any> getResult(): Result<T> = suspendCoroutine {

}

Our signature has changed quite a bit. Since we’re using a coroutine, we need the suspend modifier. But we’re getting somewhere, if you write this code, there won’t be any errors, you can return a coroutine as a Result<T>, and handle the data from inside the coroutine.

Usually we get the response by using a Call. So let’s add a call and some logic for the data handling.

suspend fun <T : Any> getResult(call: Call<T>): Result<T> = suspendCoroutine {
val data = call.execute()?.body()

if (data != null) {
it.resume(Success(data))
} else {
it.resume(Failure(NullPointerException()))
}
}

This isn’t the best approach, mostly because we call execute() instead of enque(), forcing the request. Could we do this async?

Firstly, sorry for lying about the “no callbacks” part. There will be a single callback in use here:

suspend fun <T : Any> getResult(call: Call<T>): Result<T> = suspendCoroutine {
call.enqueue(object : Callback<T> {

override fun onFailure(call: Call<T>?, error: Throwable?) = it.resume(Failure(error))

override fun onResponse(call: Call<T>?, response: Response<T>?) {
response?.body()?.run { it.resume(Success(this)) }
response?.errorBody()?.run { it.resume(Failure(HttpException(response))) }
}
})
}

We’ve added the dreaded callback, but it’s far out of sight and out of mind. If we use this API, we’ll never think about the code behind the scenes.

Some things for the API need to change. I want this to be an extension (instead of a global function), and only on T :Mappable <R> (so we know we can map it later).

suspend fun <T : Mappable<R>, R : Any> Call<T>.getResult(): Result<T> = suspendCoroutine {
enqueue(object : Callback<T> {
override fun onFailure(call: Call<T>?, error: Throwable?) {
it.resume(Failure(error))
}

override fun onResponse(call: Call<T>?, response: Response<T>?) {
response?.body()?.run { it.resume(Success(this)) }
response?.errorBody()?.run { it.resume(Failure(HttpException(response))) }
}
})
}

Now it’s a bit more clear what we’re using! :]

The grand finale

We’ve fully defined our API. What does a request look like?

suspend fun getUser(id: String): Result<User> = async {
val result = tasksApiService.getUser(id).getResult()

when (result) {
is Success -> result.data.mapToResult()
is Failure -> result
}
}.await()

This is the interaction layer. We’ve got sequential code style, we’ve got parsing and mapping down to the point, and we know which result we’re processing later on!

fun getUserProfile() = async {
val result = backend.getUser("someId")

when (result) {
is Success -> showData(result.data)
is Failure -> handleError(result.error)
}
}

Et voila! Our ViewModel just asks for the User (inside a coroutine builder), and processes it later on. :]

I wanted to go a tad further, and see whether or not we could implement some Rx-like operators on the result. So I’ve came up with a few neat extension functions which may help us in the future:

inline fun <T : Any> Result<T>.onSuccess(action: (T) -> Unit): Result<T> {
if (this is Success) action(data)

return this
}

inline fun <T : Any> Result<T>.onError(action: (Throwable) -> Unit) {
if (this is Failure && error != null) action(error)
}

inline fun <T : Any, R : Any> Result<T>.mapOnSuccess(map: (T) -> R) = when (this) {
is Success -> Success(map(data))
is Failure -> this
}

From this point we can easily use our result in a clean and fluent way. If we get a result we can map it over and over again to something more usable in our business layer.

result.mapOnSuccess { it.jobOffers }
.mapOnSuccess { it[0] }
.mapOnSuccess { it.id }

Like so. Mapping a list of Job offers to the first item’s id.

Or we could just add Success and Error handles without a sweat.

result.onSuccess { data -> showOffers(data.jobOffers) }
.onError { error -> handleError(error) }

Having a nice way of depicting what we want to do with each case.

To sum it up

We’ve seen how Android networking can look when we use the default handles, and how it can be improved, without losing functionality or damaging performance/stability.

We’ve also managed to leverage the language features to improve our code, without using third party libraries. So by seeing this you know you that anyone can build a nice API for themselves, without much work.

P.S. I’ve cooked this solution up with the help of my Android team, and with some insight into how other platforms handle their network response. Only after we’ve built everything from the ground up have we looked up similar solutions, like this one.

This solution is really good, and handles most of the things we’ve talked about here. One difference is that we are returning coroutines, whereas the other solution returns a cancellableCoroutine, and its error handling cases are split into two types, whereas we use just one. Also we’ve got some Rx-like operators and functions that we use to help us clean our code.

If you have any advice on how to make our API better, or how to extend our example’s functionality, please leave a comment, and let’s discuss it. :]

Filip Babić is an Android developer at COBE and a Computer Science student at FERIT, Osijek. He is a huge Kotlin fan, and occasionally holds mini work-shops and Kotlin meet-ups in Osijek. He likes to learn new stuff, play DnD and write about the things he loves the most. When he’s not coding, writing about coding, learning to code, or teaching others how to code, he nurtures his inner nerdiness by gaming and watching fantasy shows.

While you are here, check more articles from COBE:

--

--

Writer for

Android developer. Praise Kotlin :] Keen and enthusiastic learner and mentor, passionate about teaching and helping others. GDE @ Android, Osijek, Croatia.