Chapters

Hide chapters

Kotlin Coroutines by Tutorials

Third Edition · Android 12 · Kotlin 1.6 · Android Studio Bumblebee

Section I: Introduction to Coroutines

Section 1: 9 chapters
Show chapters Hide chapters

16. Networking With Coroutines
Written by Luka Kordić

Almost every Android app today has some form of network communication. User management, like login and registration, fetching news feed from your backend and uploading a profile picture are just a few of the most common tasks that require you to write networking code. That said, it’s important to keep your UI thread free to do other work while your app communicates with the server. Because networking code is quite common in Android apps, you want to make sure it is:

  • Performant - not blocking the main thread
  • Easy to read
  • Maintainable
  • Testable

In this chapter, you’ll see several ways of doing network calls via Retrofit library. First, you’ll make the standard API call with the callback-style approach and Retrofit’s Call as a return type. Then, you’ll replace that with a much more readable and shorter approach - using the power of coroutines and suspend functions.

Getting Started

For this chapter, you’ll use the same Disney API from previous chapters to obtain a list of characters and present it to the user. To start, open the starter project for this chapter and inspect the code. Focus on the following files:

  • DisneyApi.kt in data/networking package
  • DisneyApiService.kt in data/networking package
  • DisneyActivity.kt in ui/activity package

DisneyApi.kt is the interface to use when creating Retrofit instance. DisneyApiService.kt contains the implementation of the API calls defined in the interface. DisneyActivity.kt represents a simple screen with the basic RecyclerView setup for displaying a list of Disney characters.

Network Call With Callbacks

To check out the callback-style based implementation, start with DisneyApi.kt file. You’re going to use getCharacters, which looks like this:

@GET("characters")
fun getCharacters(): Call<CharactersResponse>

It’s a simple method for executing a GET request, and it returns CharacterResponse wrapped in Retrofit’s Call type. Now, open DisneyApiService.kt and check out the implementation of getCharacters:

fun getCharacters(
  onError: (Throwable) -> Unit,
  onSuccess: (List<DisneyCharacter>) -> Unit
  ) {
  // Make an asynchronous request
  disneyApi.getCharacters().enqueue(object : Callback<CharactersResponse> {
    override fun onResponse(
      call: Call<CharactersResponse>,
      response: Response<CharactersResponse>
    ) {
      // Invoke onSuccess lambda when the results are ready
      val data = response.body()
      if (data == null) {
        onError(Throwable("No response"))
      } else {
        onSuccess(data.data)
      }
    }

    override fun onFailure(call: Call<CharactersResponse>, t: Throwable) {
      // Invoke onError if an error happens
      onError(t)
    }
  })
}

It’s a standard way of making network requests with Retrofit. You trigger an asynchronous request by calling enqueue and passing in an anonymous implementation of Callback<CharactersResponse>. Inside of onResponse, you invoke onSuccess lambda if the response body contains the data you requested. If an error occurs, you invoke onError.

Finally, open DisneyActivity.kt, find fetchDisneyCharacters and replace the // TODO: Add implementation here line with apiService.getCharacters(::showError, ::showResults). This triggers the actual network call and invokes showError in case of an error or showResults if data is successfully retrieved. To make sure everything works correctly, build and run.

On the intro screen, click Networking, Persistence, Jetpack and then Get Disney Characters to trigger the API call. You should see a list of characters like in the image below:

This code isn’t so bad, but you want to get rid of the callbacks and make it look a bit more like synchronous code. Go ahead and see how coroutines can help with that.

Coroutine-Powered Networking

To see how easy it is to implement coroutines for networking, and how it makes the code more understandable compared with callbacks or other mechanisms, you’re going to refactor the previous example to rely on coroutines. Go back to your DisneyApiService.kt and replace the getCharacters implementation with this one:

// 1
suspend fun getCharacters(): Result<CharactersResponse> = withContext(Dispatchers.IO) {
  try {
    // 2
    val data = disneyApi.getCharacters().execute().body()
    // 3
    if (data == null) {
      Result.failure(Throwable("No response"))
    } else {
      Result.success(data)
    }
  } catch (error: Throwable) {
    // 4 
    Result.failure(error)
  }
}

Here’s the breakdown of the code above:

  1. You must mark the method with the suspend modifier because you’re using withContext suspendable function. withContext moves the work to a background thread and suspends the coroutine until the result is ready.
  2. Wrap everything in try/catch block and call execute to make the request synchronously, storing the result in the data value.
  3. Check if the response you received contains any data and return it. Otherwise, return an error.
  4. Catch any errors that might occur.

Note: Result is a class from the Kotlin standard library that encapsulates a successful outcome with a generic value of type T or a failure with an arbitrary Throwable exception.

To complete this example, you need to change the calling code in your DisneyActivity.kt. Replace the previous fetchDisneyCharacters implementation with this piece of code:

private fun fetchDisneyCharacters() {
    lifecycleScope.launch {
      apiService.getCharacters()
        .onSuccess { showResults(it.data) }
        .onFailure { showError(it) }
    }
  }

All you do here is launch a new coroutine in the lifecycleScope of the Activity and then call the API to get the data you want. When the result is ready, you’ll show the data if it exists or an error if it happened.

The great things about this code are it looks synchronous and it’s quite easy to read and reason about. Notice that you launched the coroutine on the main thread even though you want to make a network request, which could take a long time to finish. You’re allowed to do that because you used withContext(Dispatchers.IO) in your getCharacters. This way of writing coroutine code is used to ensure main-safety. You’ll learn more about that in the final chapter. Build and run the app. The result should be the same as in the previous example.

The code in this example is quite nice and readable, but there are still some issues here. Imagine you have a lot of API calls in your app and you have to write all that boilerplate. That’s a lot of repeating code you can avoid. Keep reading to find out how.

Retrofit Meets Coroutines

Here’s a quick recap of what you had to do to switch your API call to use coroutines:

  • Use launch and withContext builders.
  • Mark the function with suspend modifier.
  • Use Dispatchers to move the API call to a background thread.

Now, check out how Retrofit’s built-in support for coroutines makes the process much easier.

Open DisneyApi.kt and add a new method looks like this:

@GET("characters?page=2")
suspend fun getCharactersSuspend(): CharactersResponse

There are two important differences between this method and getCharacters. You added a suspend modifier to it and changed the return type from Call<CharactersResponse> to just CharactersResponse. This might look like a subtle change in terms of syntax, but it’s a huge deal for your code. You changed the endpoint to get the second page of characters. This isn’t important for the example. It’s just there to change the UI a bit.

To see this, once again go back to DisneyApiService.kt and swap the previous implementation of getCharacters with this one:

suspend fun getCharacters(): Result<CharactersResponse> = kotlin.runCatching {
  disneyApi.getCharactersSuspend()
}

If you’re wondering what else you have to do for this API call to work, the answer is nothing. :]

You reduced your networking logic to just two lines of code with this change. Here’s a breakdown to see what’s going on:

You kept the suspend modifier because getCharactersSuspend is also a suspend function. You also kept the return type intact but replaced the try/catch block with runCatching to make this a bit shorter. Inside runCatching, you made the API call, and that’s it.

Because you marked getCharactersSuspend with the suspend modifier, you don’t have to worry about moving this work to a background thread manually. Retrofit will do that for you. Also, you didn’t check if the response body contains any data. That’s taken care of by Retrofit as well.

You don’t have to change anything in fetchDisneyCharacters in DisneyActivity.kt because the return type is still the same, and you’re still calling a suspend function as before. Build and run the project to see the result.

On the intro screen, click Networking, Persistence, Jetpack and then Get Disney Characters to trigger the API call. You should see a list of characters like on the image below:

Key Points

  • Prefer Kotlin Coroutines over callback-style approach to make your networking code simple and more readable.
  • Mark your Retrofit methods with suspend and return the data model you need.
  • Retrofit will do the threading for you when you use suspend.
  • Make sure to catch any potential errors in the networking layer of the app.
  • If you need some metadata about the response, use Retrofit’s Response<T> as a return type.

Where to Go From Here?

That’s it for this chapter. It’s a short one, but that’s only because coroutines make networking on Android short and sweet! :]

In the next chapter, you’ll learn how coroutines can help you obtain data from a database.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.