Why do we need Coroutines in Kotlin Android Programming?
In short, Coroutines can maximize performance without burdening the device. However, before we delve deeper into the questions above, we will start from the bottom to understand all the discussion.
Imagine running an application program. The core unit will run the application on the CPU; the work is called Process. In today’s modern era, a CPU even has a lot of cores, from dual-core to octa-core, some even up to 64-core. A supercomputer can have hundreds of thousands of cores to perform many processes, often called Multi-task.
A Process in the CPU when executing a program, each task is carried out by a segment of the CPU core unit called a Thread. One CPU core can have many threads, and each thread has its task. Threads run with a small memory to take advantage of a job by running several threads simultaneously in Parallel instead of being run by one process that will go with more significant memory; it is called Multi-threading.
But with the speed of multi-threading in processing a job, it will require more memory, and there are other problems when a process is executed in Parallel. For example, when a process is split between working in Parallel, a thread will sometimes wait for each other. Or sometimes, there is a thread that finishes faster than another thread because the tasks being carried out are different, so that thread does not run anything alias idle, also called blocking.
Therefore, instead of running multiple threads, we can run only one thread and alternately work on a task. When there is a pending task for some reason, the thread will do another task. In other words, with a single thread that is very “ diligent,” we can save memory, and work can keep going. This concept is called Concurrency.
In other words, a smartphone screen will refresh 60 times in 1 second or 1000 milliseconds, meaning 1000 divided by 60, which is 16.66…67 milliseconds, with that speed processing graphics work. The speed of concurrency can be breakneck. It can even be less than one millisecond.
Then what will happen when there are many graphics processing in the concurrency process? The application will freeze because it has not finished processing graphics for one task. Other procedures are waiting in line and other tasks being done on that thread (UI thread). To avoid this, we need a process that is not running on the UI thread to work more optimally for drawing.
With the problems described above, technically, Java is not designed for concurrency. Inspired by Goroutines from the Go language, Kotlin finally created Coroutines, an API used to perform concurrency processes, change threads, and be suspended.
How to use Kotlin Coroutines?
Visit the official Kotlin coroutine repository here. To use coroutines on Android we need to add a dependency:
At the time this article was written, the last stable version was 1.5.0. You can adapt to the latest version when you want to use it, which is usually written at the beginning of the documentation. And make sure mavenCentral() is in the list of repositories.
This time, we will explain some of the fundamental functions of coroutines, namely:
- Suspend Function
- Exception Handling
CoroutineScope is where all coroutine functions run, CoroutineScope inherits CoroutineContext, so it has all coroutine properties such as cancellation and all existing elements.
However, there is a difference between CoroutineScope() and coroutineScope(), apart from starting with a capital letter and not. The difference is also in the function, CoroutineScope() with capital letters; is used when we want to create a new scope, when there is no Job(), then this function will automatically create it.
In contrast, coroutineScope() without capital letters is used inside an existing scope and inherits its parent scope. However, the two functions have something in common, when one child fails, the other child will not be executed.
We already understand what concurrency is and how it works. When a function is suspended for some reason, a thread will shift work to another function that can be done. An ordinary function would not be able to do that.
Therefore, Kotlin created suspend function as a modifier capable of suspending to allow other functions to operate. But, of course, suspend functions can only work in other functions or coroutine scopes because that’s where the modifier is needed and other coroutine extensions such as launch, async, etc.
Job is an interface with a life-cycle. that life-cycle is helpful because, in a concurrency, a function will have a state, whether the operation is running, finished, or canceled. Here is the state in Job:
Jobs are wrapped using a builder named launch. The launch has a “fire and forgets” property which means the launch runs what’s in it without returning a value.
A job could run using the .join() extension or directly using launch without Job initialization. With .join(), we run jobs using an existing scope (parent), .join() also has another option, namely joinAll() where we can run multiple jobs without having to use .join() on each job.
Since a Job with launch returns no value, what if it requires a return value from a function? We can use Deferred. Conceptually, Deferred is a Job that returns a value. If we look at what’s inside deferred, it’s a sub-interface of Job.
When using a job we need a launch builder, unlike deferred, we need an async builder. Note that async will not execute until the await function is being called.
In coroutines, an operation can stop for two reasons; the first is because of a cancellation, the second is because of a failure/exception. When using async, we can do exception handling simply by using try-catch. But Kotlin has a more readable extension, namely runCatching(); Using this, our code can be more ravishing.
However, when we use .launch(), we can’t use try-catch because it’s “fire-and-forget” and doesn’t return a value, so the caller won’t know what’s going on.
But don’t worry, coroutines provide their function for launch, namely CoroutineExceptionHandler which will be executed when an error occurs.