What is threading?
A thread is a program’s path of execution. You can think of it as the “queue” of the execution of your lines of code.
With Android, you start with one thread, the “main” thread. Sometimes called the UI thread, this is where your code is executed by default. It’s where your views (UI) get drawn, where your click listeners get invoked and where your Activitys get created.
Many applications will require some long operations. Performing a database query or network request is common, and can take quite a long time to finish. While performing a network request, you “block” the thread it is running on. This means the thread cannot execute anything else until that network request has finished.
If you perform network requests on the “main” thread, it means that views cannot be drawn, click listeners cannot be invoked and Activitys cannot be created until that request has finished. This can give the user the impression that the app has frozen or broken.
To ensure a responsive UI, you can create another thread to perform these longer tasks on. These are separate paths of execution which run side by side the “main” thread. Leaving the main thread to continue to respond to user taps and swipes. These two threads can communicate with each other; the “main” thread can start work on the other thread.
Taking a network request as an example, we need some way of knowing, from the “main” thread, when a request has finished.
We can achieve this with a callback. When we start the network request, we give the other thread a function to execute once it has finished. This might something like this:
This will vary depending on your network implementation and architecture.
The problem with callbacks comes with more complicated use cases. What if you need to perform multiple network requests for one use case? They also can be quite hard to read and reason about, often due to the syntax.
Coroutines are a Kotlin feature which convert these callbacks for long running tasks into “sequential” code.
The keyword suspend is Kotlin's way of marking a function available to coroutines. When a coroutine calls a function marked suspend, instead of blocking until that function returns, it suspends execution until the result is ready. Then it will resume where it left off with the result. While it's suspended waiting for a result, it unblocks the thread that it's running on so other executions can take place.
Calling a suspending function
As you can see, it’s a slightly simpler call and allows you to use language features like try catch.
In this example, we will fetch a list of fruits from a local database. We will be using the repository pattern, alongside Room DB, for the data components. We will use ViewModel’s and LiveData to help on the UI side.
We aren’t going to cover the ins and outs of Room in this post, but for reference here is the DAO:
Here we have the method getFruits(), which is a suspend function. Within that function we make a call to the FruitDao to get the list of fruits. We do so within a CoroutineContext. These allow you to define which thread/thread pool (collection of threads) you want the code to be executed on. Here we have used the IO thread pool, which is a pool designated for input and output events.
In this simple case, the repository wraps the FruitLocalDataSource method, and exposes it to the UI portion of the app.
Typically you might also have a caching layer here, and it would manage the relationship between the local DB and a remote server.
First to note here is you need to get a CoroutineScope. This is how you actually start a coroutine. To create a CoroutineScope you need to create a Job. This is also what you use to cancel a coroutine, see onCleared().
Once you have your scope you can use it to start a coroutine and call your suspend function. In our ViewModel that means calling the repository and updating our LiveData field accordingly.
Threading is an essential and normal part of modern Android development. In order to achieve the high standards we have for our apps, we need to offload more and more work to separate threads.
Using coroutines will simplify your callback chains and enable you to write more sequential code. But they can take a while to get your head around and structure your code base around.
Using coroutines isn’t “better” than other more traditional methods, but is a nice solution. It’s also worth noting that coroutines are supported on Kotlin’s multi-platform framework, which could you give you an advantage in the cross-platform world.