lilt: Lightweight Interactive Learning Tool

Topic 7

Introduction to Kotlin Coroutines

This week will look at the general Kotlin concept of coroutines.

Introduction

Concurrent Programming and Multithreading

In many applications you might want to perform background tasks which could take some time to complete. For example in an Android app you may want to communicate with a web server, which could take several seconds to return a response. Even a database query could be relatively slow. Or, the app might have to perform some kind of time-consuming process such as complex mathematical calculations. These kinds of tasks could potentially cause the UI to become unresponsive as the device would be too busy handling them and would not have enough CPU time to handle user interaction with the UI.

Clearly this is unacceptable. For this reason, concurrent programming techniques have been developed. Concurrent programming allows the CPU to divide its time between different tasks, such as performing network commuication and handling user interaction with the UI. The most frequently encountered approach to concurrent programming is multithreading. With multithreading, we set up a series of threads. Threads are objects containing a sequence of instructions to be performed by the CPU. We can set up many threads, and the CPU will divide its time between all of them, allowing the statements in each thread to run concurrently. So we can have one thread to handle user interaction with the UI, another thread to communicate with a web server, and yet another to perform some complex, time-consuming processing such as intensive mathematical calculations.

Applications typically have a default, or main thread. In Android apps this is known as the UI thread - the thread on which interaction with the UI is handled. By default all code runs on the UI thread. If you want to run time-consuming code without causing the UI to become unresponsive, you therefore have to launch one or more background threads. In fact, to encourage you to put such tasks in separate threads, Android will crash your application if it detects that you are trying to communicate across the network from the main UI thread.

Coroutines

Formerly in Android, the recommended approach was to create separate threads directly. However, handling threads in code can be somewhat tricky. For this reason, a new, more lightweight approach to concurrent programming was introduced with Kotlin, namely coroutines. Coroutines are now the recommended way to perform time-consuming background tasks in Android, and are used for such tasks as network communication or querying a database. You can also create your own coroutines for other time-consuming work.

What is a coroutine?

A coroutine in Kotlin is a function that can run concurrently with other code and can be suspended. What do we mean by suspended? It means the coroutine can pause without stopping execution of other code outside it. For example, if we wanted to implement a timer, which increased a counter variable every second, we could increase the variable by one in a coroutine and then pause the coroutine for 1000 milliseconds. This would not affect the execution of code outside the coroutine, including the UI thread.

Coroutines typically use threads. However, there is not necessarily a one-to-one correspondence between coroutines and threads, in other words, if we launch a coroutine it does not necessarily mean that exactly one thread will be launched to run it in. From the Kotlin documentation (see here):

However, a coroutine is not bound to any particular thread. It may suspend its execution in one thread and resume in another one.

In fact coroutines can share the same thread. As stated in the Kotlin documentation here:

Coroutines are less resource-intensive than JVM threads. Code that exhausts the JVM's available memory when using threads can be expressed using coroutines without hitting resource limits.

Adding the coroutine library as a dependency

When using coroutines we need to include the Kotlin coroutines library as a dependency, e.g. in build.gradle.kts in IntelliJ:

In Android Studio, an Android app project will automatically include the Kotlin coroutines library.

You should use IntelliJ for exercises 1-4 and Android Studio for exercise 5.

An example of a coroutine

Coroutines must be launched. The example below features a main() which launches a coroutine with GlobalScope.launch(). The coroutine will then run concurrently with the code outside it. As you can probably see, the coroutine is a lambda function which loops from 1 to 5, and suspends for one second (i.e. delay(1000)) before printing each number.

Look at the program above. You can see it launches a coroutine with GlobalScope.launch(). The coroutine in this example is a lambda function. The coroutine loops from 1 to 5, printing each number but waiting for 1000 milliseconds before printing number (remember in the discussion above we saw that coroutines can be suspended without affecting the execution of other code).

Exercise 1

You must be logged in to attempt exercise 1.

Exercise 2

You must be logged in to attempt exercise 2.

Exercise 3

You must be logged in to attempt exercise 3.

Exercise 4

You must be logged in to attempt exercise 4.

Coroutines and Android

Coroutines are easily usable in Android and in fact the Android Kotlin APIs are designed with them in mind. One important thing to consider is that a specific scope has to be used when launching coroutines in Android. This is because, as we have already seen in Topic 3, Android activities have a distinct lifecycle. We have to ensure that the coroutine is coordinated with the app's lifecycle, for example it should be cancelled when the activity is destroyed.

In Android there are, in particular, two scopes which are useful for launching coroutines:

  • lifecycleScope, which coordinates the coroutine with the activity's lifecycle;
  • viewModelScope, which coordinates the coroutine with the lifetime of a ViewModel.

We will explore each of these with a coding exercise to create a timer.

Coroutines and Android - Coding Exercise

You are going to write simple counter app making use of coroutines. Create a new project in Android Studio and copy the starter code below into it.

You are going to implement a counter, which increases by one every second, by launching a coroutine. You are also going to add functionality to stop the coroutine by cancelling the Job returned when it is launched.

  • Add a nullable Job as an attribute of your activity, e.g:

  • In your composable hierarchy, declare a count state variable.
  • Add a Button to the provided column, labelled "Start Counter". When the user clicks on this button, a coroutine should be launched (in lifecycleScope) which delays for 1000 milliseconds and then increases the counter by one. You will need to iplement this as a loop, which keeps looping until the coroutine is cancelled. You can use the isActive property of CoroutineScope to do this, eg.:

    You should set the Job you declared earlier to the return value of your launch. Use lifecycleScope.launch to launch the coroutine: this will launch with a scope of the activity lifecycle (so when the activity is destroyed, the coroutine will be cancelled).

  • Add another Button labelled "Stop Counter". When this is clicked, the coroutine should be cancelled. Call the cancel() method of the Job.
  • Below the button, add a Text which should display the current value of the counter.
  • Try it out, does it work?

Exercise 5

You must be logged in to attempt exercise 5.