Twenty years ago, just starting a desktop computer took a long time. Applications often involved waiting for the computer to perform some operation, and network access was often an afterthought. Those times are long over. Users now expect their apps to be responsive and network-aware. Thankfully, many tools are available to create these types of responsive network apps.
In Android, coroutines are the primary means for running code in the background. They’re designed to be easy to understand and easy to use. They let you—the developer—focus on your business logic while letting the operating system manage the actual nitty-gritty of balancing system resources.
In this article, Karol Wrotniak walks you through the theory of working with coroutines. If you want to explore this, as well as network access and reactive programming, take a look at Kodeco’s Concurrency & Networking in Android course. This course will set you on the path to creating fast, responsive Android apps.
Coroutines
A coroutine is a piece of code that can be suspended and resumed. It’s important to understand that a coroutine isn’t a thread. But it does run on a thread. A coroutine can be resumed on the same thread as it was suspended or on a different one. Take a look at the following image:
Imagine that you need to visit several places in a city. You take a taxi to the bank, spend some time there, rent a scooter and go to a restaurant, and finally, take a bus home. In this case, you are a coroutine, and the taxi, scooter, and bus are the threads.
While getting things done in the bank and eating in the restaurant, you aren’t traveling; you’re suspended. The taxi, scooter, and bus don’t need to wait for you. They can serve the other customers. When you’re ready to go, you resume your travel.
In some cases, you can choose several forms of transport. But sometimes you have to use a specific one. For example, if you have a long-distance trip, you must take a bus. Traveling by scooter would be too slow. And you can’t take a taxi because it’s too expensive. In the city center, using a scooter during rush hour may be better, since the bus and taxi can get stuck in traffic jams, causing the trip to take longer.
When you can choose the kind of transport, it doesn’t matter which type of bus, taxi, or scooter serves you. In coroutines, the kinds of transport are the dispatchers. You can choose the dispatcher on which the coroutine runs, and the dispatcher gives you a thread with the desired properties. Usually, it doesn’t matter which particular instance of the thread you get.
There are some cases when you need to use a specific form of transport. For example, you can only go to the restroom on foot. Trying to use a bus or a taxi is impossible. And there’s only one instance of your foot. Similarly, there’s only one instance of the Android main thread.
If you keep adding more cars, buses and scooters to the city, the transport will be more efficient. But, at a certain point, traffic jams will appear, and the transport will become slower.
The city has a limited number of cars, buses, and scooters. Similarly, the number of threads in the app is also limited. Threads are heavyweight entities. They use memory to keep their stack and CPU cycles to run the code.
On the other hand, the limit on the number of tasks you use is much higher. Tasks don’t consume any resources like roads or parking areas. Similarly, coroutines are lightweight entities. You can have thousands of them in the app simultaneously, and it won’t affect performance like having thousands of threads, which could use up several gigabytes of RAM.
Suspending
Suspending is a way to pause a coroutine and resume it later. It’s just like you can save a game at a checkpoint. You can then go back to that checkpoint later on. You can have multiple checkpoints and return to any of them in any order.
In Kotlin coroutines, suspending can’t happen at just any place in the code. Coroutines can suspend only at suspension points. Android Studio has a special icon on the left side of the editor that shows suspension points. It looks like this:
Suspension points are invocations of suspending functions, which are denoted by the suspend
modifier. As a limitation to coroutines, you can only call suspending functions from another suspending function or a coroutine. You’ll get a compilation error if you try to call a suspending function in a regular function.
You can place the suspend
modifier on a function that doesn’t have any suspension points. The code will compile, but the compiler will trigger a warning.
Building Coroutines
To start your first coroutine in your program, you must use one of the coroutine builders. They take a lambda as an argument, describing what code block will run inside the coroutine. The simplest example looks like this:
runBlocking { doSuspendableWork() // this is a suspending function }
What’s important here is that calling the coroutine builder itself isn’t a suspendable operation. So, you can call it from any function. The lambda passed to the builder is a suspendable block of code so that you can call suspendable functions from it. The builder executes the lambda in the coroutine at some point in the future.
There are three basic coroutine builders in Kotlin: launch, async, and runBlocking.
runBlocking
The simplest is the runBlocking builder. It blocks the current thread until the coroutine completes. There are no advantages to suspensions in this case. During the period when the coroutine is suspended, the thread is blocked. It consumes the resources but doesn’t do any useful work.
Developers rarely use the runBlocking in production code of real Android apps. It can be useful to integrate newly-written suspending code with existing blocking code, which doesn’t use coroutines, e.g.in a legacy app beginning to adopt coroutines. When you write Android projects from scratch, this won’t be the case—you’ll write with coroutines from the start. Most modern, popular Android libraries now use coroutines. Simple console apps are another legitimate use case of a runBlocking.
runBlocking is sometimes used to call suspending functions from unit test methods. However, there’s a dedicated runTest builder, which is more suitable for testing.