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

14. Coroutines & Android
Written by Luka Kordić

In the previous sections, you learned about the ins and outs of the Coroutine API. In this one, you’ll apply that knowledge to an Android project. Also, you’ll see how coroutines provide an easy solution for some of the common problems in the Android world.

The Importance of the Android Main Thread

Android developers discovered concurrency’s importance quite early. Android is inherently asynchronous and event-driven, with strict requirements for which threads certain things can run on. When a user launches an app, Android creates a process along with an execution thread - the main thread, sometimes referred to as the UI thread.

The main thread handles drawing, reacting to user events, receiving events from other apps and basically everything that happens onscreen. To keep everything running smoothly, it’s important not to overload the UI thread with additional work. If the UI thread is busy doing other work, it can’t do its job of drawing views on the screen. This might cause jerky animations, block the screen or even cause the infamous Application Not Responding (ANR) error. The only way to create a responsive app is by leaving the UI thread as free as possible by having background threads do all the hard work asynchronously.

Getting Started

In this chapter, you’ll learn what mechanisms exist for asynchronous programming on the Android platform and why coroutines perform much better. You’ll see what Kotlin coroutines bring to the table and how they simplify various facets of Android development. To see this in action, you’ll use a simple Disney Explorer app. The app fetches Disney characters from the API and lists them.

To start, download the starter project for this chapter and open it in Android Studio. Run the project. You should see a screen like this one:

Intro screen
Intro screen

Once you start to browse the code, you’ll notice the project has a lot of pre-written code. That’s because you’ll use this project for all the following chapters. For this chapter, you only need to focus on a few files:

  • DependencyHolder.kt in the di package: This file holds all the dependencies you’ll need while building the app.

  • BackgroundProcessingActivity.kt in the ui/activity package: This is the Android Activity class where everything is wired to make the API calls using various async constructs.

  • DisneyApiService in data/networking package: You’ll use this interface to make a network request to fetch Disney characters.

BackgroundProcessingActivity.kt has ProcessingMethod enum. To change the processing method in the code, you’ll change its processingMethod property to one of the values defined in ProcessingMethod. Depending on the currently set value, the app will use one of the predefined methods to do some work.

To open the screen for this chapter, click the Background Processing Examples button on the intro screen. You should see this screen:

Below the RW logo, text shows the currently selected processing method. In the center, you can see an animating spinner, which will show the impact of running work in the UI thread. On the bottom, there’s the Start processing button that runs the work when clicked.

Doing Heavy Work on UI Thread

Now, you’ll see what happens when the main thread is busy doing heavy work and can’t do its primary job of rendering the UI. Open BackgroundProcessingActivity.kt and find the processingMethod property. Make sure its value is set to MAIN_THREAD, like this:

private var processingMethod: ProcessingMethod = MAIN_THREAD

Run the app and open Background Processing Examples, then click Start processing. This will invoke runUiBlockingProcessing, which looks like this:

private fun runUiBlockingProcessing() {
    // This will block the thread while executing
    showToast("Result: ${fibonacci(40)}")
  }

And fibonacci has the following naive implementation:

private fun fibonacci(number: Int): Long {
    return if (number == 1 || number == 2) {
      1
    } else {
      fibonacci(number - 1) + fibonacci(number - 2)
    }
  }

runUiBlockingProcessing starts a calculation of the 40th Fibonacci sequence number. Because the processing happens on the UI thread, you’ll notice the animating spinner stops until the calculation finishes. When the calculation completes, you’ll see a toast message with the computed value and the spinner will animate again.

The result should look like this:

In this example, you saw how doing heavy, long-running work on the main thread can seriously affect your app’s performance. Common long-running tasks include decoding a bitmap, accessing storage, processing large collections or performing network requests. In the rest of this chapter, you’ll learn different ways of switching heavy work from the main thread to a background thread.

Thread

A thread is an independent path of execution in a program. The Java Virtual Machine allows an application to have multiple threads of execution running concurrently. There are two ways to create a new thread of execution.

  1. Extending the Thread class:
// Creation
class MyThread : Thread() {
override fun run() {
  doSomeWork()
  }
}
// Usage
val thread = MyThread()
thread.start()
  1. Passing a Runnable interface implementation as the Thread constructor parameter:
// Creation
class MyRunnable : Runnable {
  override fun run() {
    doSomeWork()
  }
}
  // Usage
val runnable = MyRunnable()
val thread = Thread(runnable)
thread.start()

To see a real example, in your BackgroundProcessingActivity.kt, change processingMethod property value to THREAD.

private var processingMethod: ProcessingMethod = THREAD

With this setup, your app will use runProcessingWithThread when the Start processing button is clicked. Here’s the method’s implementation:

private fun runProcessingWithThread() {
    // Create a new Thread which will do the work
    Thread(getCharactersRunnable).start()
  }

getCharactersRunnable is a private property defined like this:

private val getCharactersRunnable by lazy { GetCharactersRunnable() }
inner class GetCharactersRunnable : Runnable {
    override fun run() {
      val characters = disneyApiService.getDisneyCharacters()
      runOnUiThread { showResults(characters) }
    }
  }

You created GetCharactersRunnable class, which implements the Runnable interface and overrides run. In run, you define the work the program will execute when it invokes the method. You store an instance of GetCharactersRunnable as the private property and pass it as an argument to the Thread constructor. Finally, you call start on the Thread instance to begin its execution.

Run the app, and click Background Processing Examples and then Start processing.

You’ll see the animation still runs while characters are being downloaded. You might notice a short stop in the animation when you click Start processing. This happens because thread switching doesn’t occur instantly and comes with a small cost. It’s important to note how the downloaded list of characters has been passed to the UI thread using the runOnUiThread function you inherit from the Activity class.

Interacting with UI components from a background thread would’ve caused an error like this:

com.raywenderlich.android.disneyexplorer E/AndroidRuntime: FATAL EXCEPTION: Thread-2
    Process: com.raywenderlich.android.disneyexplorer, PID: 13419
    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

In a nutshell, threads might be:

  • Expensive: Context switching and having upper limits in the number of threads that can be spawned.
  • Difficult: Creating a multithreaded program is quite complex, requiring a lot of ceremonies around how the code is referenced and executed across threads.

Handler

Handler is a part of the HaMeR Framework (Handler, Message and Runnable). It allows you to send and process Message and Runnable objects. Every Handler instance is associated with a single thread and bound to that thread’s Looper. A message queue associated with a Looper can accept messages and runnables from Handler, which then execute on that looper’s thread. Handlers are commonly used in two scenarios:

  1. To schedule messages and runnables for future execution.
  2. To enqueue some action to be performed on another thread.

How can you use all this to send data from a background thread to the UI? You just need a Handler associated with the main looper that’s available by calling Looper.getMainLooper() and then post an action as a Runnable:

val runnable = Runnable {
    // update the ui from here
}
val handler = Handler(Looper.getMainLooper())
handler.post(runnable)

The different objects’ responsibilities are:

  • Looper: Runs a loop on its Thread, waiting for Message instances on its MessageQueue.
  • MessageQueue: Holds a list of messages for a given Thread.
  • Handler: Allows the sending and processing of Message and Runnable to the MessageQueue. Use it to send and process messages between threads.
  • Message: Contains the description and data you can create and send using a Handler.
  • Runnable: Represents a task to execute.

To see a working example, in your BackgroundProcessingActivity.kt, set private var processingMethod: ProcessingMethod = HANDLER. This makes sure that, when you click Start processing, runProcessingWithHandler is called. Here’s the method definition:

private fun runProcessingWithHandler() {
  // Create a Handler associated with the main thread
  val handler = Handler(Looper.getMainLooper())
    
  // Create a new thread and give it some work to do
  Thread {
    val characters = disneyApiService.getDisneyCharacters()
  
    // Use the handler to show results on the main thread
    handler.post {
      showResults(characters)
    }
  }.start()
}

Run the app and click Start processing on the Background Processing Examples screen.

The left screenshot shows you’re currently using the handler method for processing. You’ll also notice the animating spinner won’t freeze when you start processing the request. The right screenshot shows the result that’s passed from the background thread to the UI thread via Handler.

HandlerThread

The UI thread already comes with a Looper and a MessageQueue. For other threads, you need to create the same objects if you want to leverage the HaMeR framework. Do this by extending the Thread class as follows:

// Preparing a Thread for HaMeR
class MyLooperThread : Thread() {
  lateinit var handler: Handler
  
  override fun run() {
    // adding and preparing the Looper
    Looper.prepare()
    // the Handler instance will be associated with Thread’s Looper
    handler = object : Handler() {
      override fun handleMessage(msg: Message) {
        // process incoming messages here
        
      }
    }
    // Starting the message queue loop using the Looper
    Looper.loop()
  }
}

But it’s more straightforward to use a helper class called HandlerThread, which creates a Looper and a MessageQueue for you. Check out the implementation of runProcessingWithHandlerThread inside BackgroundProcessingActivity.kt.

private fun runProcessingWithHandlerThread() {
  // Create a new thread
  handlerThread = HandlerThread("MyHandlerThread")

  handlerThread?.let {
    handlerThread?.start()
    
    // Create a new Handler that will use HandlerThread's Looper to do its work
    val handler = Handler(it.looper)
    
    // Create a Handler with the main thread Looper
    val mainHandler = Handler(Looper.getMainLooper())
    
    // This will run on the HandlerThread created above
    handler.post {
      val characters = disneyApiService.getDisneyCharacters()
      
      // Use the handler associated with the main thread to show the results
      mainHandler.post {
        showResults(characters)
      }
    }
  }
}

Here, you create an instance of HandlerThread, passing a name that’s useful for debugging purposes. HandlerThread extends the Thread class, and you must start it to use its Looper. You then access the thread’s looper property and pass it as the constructor parameter to Handler. You then can use the newly created handler to send Runnable objects to the HandlerThread.

To test this behavior, change the processing method to the following:

private var processingMethod: ProcessingMethod = HANDLER_THREAD

Run the app and click Start processing.

As before, you’ll see the animation runs without stuttering. The list of Disney characters gets downloaded in a background thread, which was created with HandlerThread. It then gets passed to the UI thread for rendering via the Handler instance that’s bound to the main thread Looper.

Executors

The Executor interface helps to decouple submission of Runnable tasks from the mechanics of how each task will run, which thread will be used, how it will be scheduled, etc.

You’ve seen that you can encapsulate code into a Runnable implementation to eventually run it in a given Thread. Every object that can execute what’s defined as a Runnable can be abstracted using the Executor interface, introduced in Java 5.0 as part of the concurrent APIs.

interface Executor {
    fun execute(command: Runnable)
}

You can execute a Runnable in many ways. For instance, you can directly invoke the run() method or pass the Runnable object as a constructor parameter of the Thread class and start it, as seen previously. In the former case, you’re executing the runnable code in the caller thread. In the latter, you’re executing the same code into a different thread. This depends on the particular Executor implementation.

Creating a thread is simple in code but expensive in practice. Every time you create a Thread instance, you must request resources from the operating system, and every time the thread completes its job — when its run() method ends — it must be garbage collected. The typical solution involves thread pools, which need some kind of lifecycle.

You must initialize the pool with a minimum number of threads. When the application ends, the pools should shut down and release all resources. Even when the pool is active, you can have a different policy for the minimum number of threads to keep alive or how to manage the creation of new instances when needed. You could limit the number of threads, forcing the client to wait, or create a new thread every time you need to run something.

Besides the simple Executor interface, you also can use the ExecutorService interface. The ExecutorService is then the abstraction for a specific Executor, which you must initialize and shut down to allow for the efficient and optimized execution of Runnable objects. The way this happens depends on the specific implementation. One of the most important classes is the ThreadPoolExecutor. It manages a pool of worker threads and a queue of tasks to execute. Depending on the configured policy, it reuses an available thread or creates one to consume the tasks from a queue.

The concurrent APIs provide different implementations available through some static factory methods of the Executors class. The most common are Executors.newSingleThreadExecutor(), which creates an executor that will process a single task at a time, and Executors.newFixedThreadPool(N), which creates an executor with an internal pool of N threads.

It’s important to note that an ExecutorService also provides the option of executing Callable<T> implementations. Whereas the Runnable interface defines a run() method, which returns Unit, a Callable<T> is a generic interface, which defines the call() method that returns an object of type T:

interface Callable<T> {
    fun call(): T
}

Think of a Callable<T> as a Runnable that returns an object of type T at the task’s end. You can ask the ExecutorService to run the given Callable<T> using the invoke() method, getting a Future<T> in return. The Future<T> provides a get() method, which blocks until the result of type T is available or throws an exception in case of error or interruption.

Sample Usage

val executor = Executors.newFixedThreadPool(4)
(1..10).forEach {
  executor.submit {
    print("[Iteration $it] Hello from Kotlin Coroutines! ")
    println("Thread: ${Thread.currentThread()}")
  }
}

This code sample is rather simple. First, it creates a new ExecutorService by using newFixedThreadPool(4). This creates a pool of four threads that will operate on the given tasks. Then, you create a range from 1 to 10 and iterate over it. In each iteration, you submit a new task that prints the current range value and the current thread’s name.

Now, you’ll see how you can use executors to fetch the list of characters in the app. Like before, open BackgroundProcessingActivity.kt and change processingMethod to HANDLER_THREAD.

Then, navigate to runProcessingWithExecutor method. Its implementation looks like this:

private fun runProcessingWithExecutor() {
  // Create a new Executor with a fixed thread pool and execute the Runnable
  val executor = Executors.newFixedThreadPool(1)
  executor.execute(getCharactersRunnable)
}

Like the example above, you create a new ExecutorService with Executors.newFixedThreadPool(1). This time, you don’t need multiple threads in the pool because you want to make only one network request. With the service created, you call execute on it and pass it an instance of Runnable as an argument. getCharactersRunnable is the same instance of GetCharactersRunnable from the section about threads.

Run the app and check the results.

As with the previous examples, when you click the Start processing button, you’ll see the animation running and the network request being made in the background, eventually showing the character list on the screen.

The main advantages of using ThreadPoolExecutor in an Android application are that it:

  • Serves as a powerful task execution framework because it supports task addition in a queue, task cancellation and task prioritization.
  • Reduces the overhead associated with thread creation as it manages a required number of threads in its thread pool.
  • Lessens boilerplate code as it abstracts most of the codebase behind factory methods with sane defaults.

However, although ExecutorService implementations provide an optimized usage of threads in terms of creation and reuse, they don’t solve the problems related to context switching between threads.

RxJava

Reactive programming is an asynchronous programming paradigm concerned with data streams and change propagation. The essence of reactive programming is the observer pattern.

Note: The observer pattern is a software design pattern wherein data sources or streams, called observables, emit data and one or more observers, who are interested in getting the data, subscribe to the observable.

In reactive programming, you can create data streams from anything including Array, ArrayList, etc. These data streams can be:

  • Observed

  • Modified

  • Filtered

  • Operated on

  • Used as an input to another one. You can even use multiple streams as inputs to another stream.

  • Merged

  • Filtered to get another one with only events you’re interested in.

  • A source of data values mapped to another stream.

A typical data stream can emit three values: when the event occurs, an error occurs or the event finishes.

RxJava is a library that makes it easier to implement reactive programming principles on any JVM-based platform, including Android. To manage threads, RxJava has a helper class called Schedulers. Schedulers are how you tell where the observer and observables should run.

Some general types of Schedulers to observe:

  • Schedulers.computation(): Used for CPU intensive tasks.
  • Schedulers.io(): Used for IO bound tasks.
  • Schedulers.from(Executor): Used with custom ExecutorService.
  • Schedulers.newThread(): It always creates a new thread when a worker is needed.

This is where RxAndroid library comes into the picture, playing a significant role in supporting multi-threading concepts in Android applications. It provides a Scheduler that schedules on the main thread or any given Looper.

Sample Usage

Observable.just("Hello", "from", "RxJava")
      .subscribeOn(Schedulers.newThread())
      .observeOn(AndroidSchedulers.mainThread())
      .subscribe(/* an Observer */);

This code creates a new Observable, which emits as a stream of three strings that are passed as arguments to just. subscribeOn specifies the values will be produced on a new thread, and observeOn means the observer will get the results on the main thread.

It’s time to try RxJava in your app. Navigate to runProcessingWithRxJava in BackgroundProcessingActivity.kt. Here’s the method implementation:

private fun runProcessingWithRxJava() {
    // 1
    disposable = Single.create<List<DisneyCharacter>> { emitter ->
      // 2
      val characters = disneyApiService.getDisneyCharacters()
      emitter.onSuccess(characters)
      // 3
    }.subscribeOn(Schedulers.io())
      // 4
      .observeOn(AndroidSchedulers.mainThread())
      // 5
      .subscribe(::showResults, Throwable::printStackTrace)
  }

Here’s a breakdown of what’s going on in this code block:

  1. You create a new instance of Single, which will emit items of type List<DisneyCharacter>. Single is an observable construct that can emit a single value or it can fail with an error.
  2. Make a network request and emit the result when it’s ready.
  3. Use Schedulers.io() to switch the execution of the network request to the background thread.
  4. Use AndroidSchedulers.mainThread() to observe the results on the UI thread.
  5. Call subscribe to trigger the reactive stream and pass in two method references as arguments. showResults will be invoked if the request was successful, and Throwable::printStackTrace will be invoked in case of an error. A call to subscribe returns an instance of Disposable. You store that into the disposable value, which you then will use to clean resources in onDestroy with this:
// Dispose the disposable if it has been created
    disposable?.dispose()

Set private var processingMethod: ProcessingMethod = RX_JAVA and run the app again.

Once again, you can see the animation is running while the network request is being made. After a while, you’ll see the list of characters rendered on the screen.

Note: The topic of reactive streams is pretty vast. Covering the mechanics of its functionalities is beyond this book’s scope.

Although reactive programming solves many complex concurrency problems, the learning curve for RxJava is quite steep. It’s a different approach to programming and can lead to confusion when programming larger apps.

Coroutines

Now that you have a clear idea about various ways of doing asynchronous work in Android, as well as some of the pros and cons, it’s time to come back to Kotlin coroutines. Kotlin coroutines are a way of doing things asynchronously in a sequential manner. Creating coroutines is quite cheap compared to creating threads.

Note: Coroutines are implemented entirely through a compilation technique (you don’t need any support from the VM or OS side), and suspension works through code transformation.

Coroutines are based on the idea of suspending functions that can stop the execution when they’re called and make it continue once it has finished running their own task. Enabling Kotlin coroutines in Android involves just a few simple steps. To show how easy it is to enable coroutines, head to the starter project and add the Android coroutine library dependency into your app’s build.gradle file under dependencies block, replacing the line // TODO: Add Kotlin Coroutine Dependencies here with the following:

// Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0'

Next, inside your BackgroundProcessingActivity.kt, add the following implementation for the method runProcessingWithCoroutines:

private fun runProcessingWithCoroutines() {
  // Create a new coroutine in the scope tied to the lifecycle of the Activity
  lifecycleScope.launch(Dispatchers.IO) {
    // Make the network request on a background thread
    val characters = disneyApiService.getDisneyCharacters()
    // Switch to the main thread to show the results
    withContext(Dispatchers.Main) {
      showResults(characters)
    }
  }
}

You probably already understand what’s going on in this code block, but here’s a quick recap:

  • First, you create a new coroutine in a scope tied to the lifecycle of the Activity. You’re going to learn more about lifecycleScope in the next chapter.
  • While creating the coroutine, you pass in Dispatchers.IO as an argument to move the execution to a background thread.
  • You then trigger the network request and store the result in the characters variable.
  • When the result is ready, switch to the main thread using Dispatchers.Main and show the results.

One last time, set private var processingMethod: ProcessingMethod = COROUTINES and run the app to see the previously written method in action.

As with all the other methods, you can see the network request is executed and the list is rendered on the UI without blocking the main thread.

Here are some of the advantages to using coroutines over the other methods:

  • Coroutines allow you to write asynchronous code synchronously. This makes the code much easier to grasp and read.
  • Creating coroutines is much cheaper than creating threads, and you can create many coroutines.
  • You can use predefined thread pools by utilizing Dispatchers.
  • It’s easy to keep track of coroutines with scopes.

You’ve already learned a lot about the mechanics of Kotlin coroutines in previous chapters. In subsequent chapters, you’ll cover their usage in different layers of Android apps.

Key Points

  • Android is inherently asynchronous and event-driven, with strict requirements as to which thread certain things can happen on.
  • The UI thread — a.k.a., main thread — is responsible for interacting with the UI components and is the most important thread of an Android application.
  • Almost all code in an Android application will be executed on the UI thread by default. Blocking it would result in a non-responsive application state.
  • Thread is an independent path of execution within a program allowing for asynchronous code execution. But it’s highly complex to maintain and has usage limits.
  • Handler is a helper class provided by the Android SDK to simplify asynchronous programming. But it requires many moving parts to set up and get running.
  • HandlerThread is a thread that’s ready to receive a Handler because it has a Looper and a MessageQueue built into it.
  • Executors is a manager class that allows running many different tasks concurrently while sharing limited CPU time, used mainly to manage thread(s) efficiently.
  • RxJava is a library that makes it easier to implement reactive programming principles on the Android platform.
  • Coroutines make asynchronous code look synchronous and work pretty well with the Android platform out of the box.

Where to Go From Here?

Phew! That was a lot of background on asynchronous programming in Android. But the good thing is you made it.

In the upcoming chapters, you’ll dive deeper into how to leverage coroutines in Android apps to handle async operations while keeping in sync with various nuances of the Android platform, such as respecting lifecycles of an app and efficient context switching to facilitate the various use cases of apps to fetch-process-display data.

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.