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ć

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

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.

Intro screen
Ubjza rfdeom

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
private fun runUiBlockingProcessing() {
    // This will block the thread while executing
    showToast("Result: ${fibonacci(40)}")
  }
private fun fibonacci(number: Int): Long {
    return if (number == 1 || number == 2) {
      1
    } else {
      fibonacci(number - 1) + fibonacci(number - 2)
    }
  }

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.

// Creation
class MyThread : Thread() {
override fun run() {
  doSomeWork()
  }
}
// Usage
val thread = MyThread()
thread.start()
// Creation
class MyRunnable : Runnable {
  override fun run() {
    doSomeWork()
  }
}
  // Usage
val runnable = MyRunnable()
val thread = Thread(runnable)
thread.start()
private var processingMethod: ProcessingMethod = THREAD
private fun runProcessingWithThread() {
    // Create a new Thread which will do the work
    Thread(getCharactersRunnable).start()
  }
private val getCharactersRunnable by lazy { GetCharactersRunnable() }
inner class GetCharactersRunnable : Runnable {
    override fun run() {
      val characters = disneyApiService.getDisneyCharacters()
      runOnUiThread { showResults(characters) }
    }
  }

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.

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:

val runnable = Runnable {
    // update the ui from here
}
val handler = Handler(Looper.getMainLooper())
handler.post(runnable)
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()
}

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()
  }
}
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)
      }
    }
  }
}
private var processingMethod: ProcessingMethod = HANDLER_THREAD

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.

interface Executor {
    fun execute(command: Runnable)
}
interface Callable<T> {
    fun call(): T
}

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.

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

RxJava

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

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.

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)
  }
// Dispose the disposable if it has been created
    disposable?.dispose()

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.

// Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0'
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)
    }
  }
}

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.

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.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now