Asynchronous programming with Kotlin Coroutines

Tram Ho

One of the interesting features of Kotlin is coroutines. During the development of Android apps, managing threads is always a challenge because there are limitations in handling UI or Main threads. And it is common practice that we will have to work a lot with heavy tasks such as performing network tasks, handling complex logic, … for a very bad user experience. So we often create private threads running in parallel with the main thread to do this job. To manage these threads we have many ways, nowadays there are many 3rd party libraries that are born to help us manage multithreading, which must be mentioned as RX. However, Kotlin has provided a distinct choice for developers, Coroutines. Coroutines provides the simplest solution for handling and managing threads. Coroutines offer nothing but lightweight threads. They provide us an easy way to do synchronous and asynchronous programming. Coroutines allow us to replace callbacks and not block UI.

1. Use Suspend Function

Let’s learn about Suspend Function by taking a simple example of getting user details from a local API or database and showing them to the user. If we try to get data through the API on the Main Thread, it will throw a NetworkOnMainThreadException. If we fetch the read data from the local database, this will cause the Main Thread block, the Main Thread will stop its operations until the data is fetched, resulting in a zero experience. good for users.

We can do this simply by using suspenfunctions in coroutines. suspend is a tag tag. Methods that are suspendable are interpreted as methods that run synchronously, but are not immediately returned. The suspend functions are suspended until a result is returned. Thus while suspend functions are called, heavy requests will not block the main thread.

Imagine it more clearly:

When a method is marked as suspend, that method will not execute. It has been paused. And when it continues, it will start from the moment it stopped. When we call a suspend function on the main thread, that function is suspended and does its work on any other worker threads. Once done, it will continue with the result so we can use the result on the Main Thread.

But there are some limitations when using suspend functions. They can be called inside a coroutine builder or from another suspend function.

Dispatchers are used to specify which thread will do the suspend funtcion job. These are similar to schedulers in Rx. We can specify which Dispatchers we want to make the fetchUser API request (in our case, Dispatchers.IO ). We use the withContext method to specify this:

Once the API request is completed, we can use the result on main using Dispatchers.Main. The different Dispatchers available are:

Dispatchers.Default: If you don’t explicitly specify, Dispatches.Default will be called

Dispatchers.IO : This Dispatches is designed to handle disk IO blocking operations and network operations.

Dispatchers.Main: Dispatches process is limited to the main thread, interacting directly with the UI. Accessing this property can throw an IllegalStateException if no Dispatches are present in the classpath. It is mainly used to process data called from API or databse

Use the appropriate Dispatches based on your request.

2. Coroutines processing

The Kotlin compiler uses callbacks when computation can be paused. Coroutines call these callbakcs continuation. The next part is nothing but a generic callback interface with some additional information in it. The status machine will look like this:

3. Structured Concurrency

Structured Concurrency is a prototype system designed in coroutines that tries to address memory leaks. And it helps us to think about the following points:

  1. Who can cancel a job in progress?
  2. Coroutine life cycle?
  3. Who handles errors when a job fails?

3.1 Coroutine Scope

CoroutineScope is an interface in the kotlinx.coroutines package.

  1. Keep track of the processes.
  2. It provides the ability to cancel the coroutine process.
  3. It will be notified whenever an error occurs. Coroutine Scope does not contain references to any heavy Objects. So whenever we want to control coroutine’s lifecycle, we need to create a scope. It can be created easily as follows:

Now the coroutines created above using the launch coroutine launch will follow the life cycle of the scope. If any exception arises, it will be notified so we can take care of it. In many cases when a user exits the screen while the network request is in progress, we need to cancel all ongoing requests. So in the ViewModel , onCleared () callback , or in the onDestroy () operation callback, we can scoped so all coroutines stop executing them.

4. Handling Exception with coroutine scope

A job that can be used to define life cycles of scope and coroutine. If we pass a job to scope, it will handle the exceptions in a specific way. When there are multiple child elements associated with scope:

If any of the jobs fails, it will propagate exceptions to other jobs.

When an error is reported, the scope will self-destruct and fire an exception.

Pausing other jobs and throwing an exception is not ideal in most cases of failure, often resulting in conflic. For these cases, we need to use SupervisorJob .

When we are using SupervisorJob, it will not prevent other tasks from being performed in the same scope. And when an error is reported, the scope will do nothing. Since the exception can be common, we may have to use a try / catch block in our code for safety.

I will write a separate article about handling exceptions of coroutines, scope, etc.

5. How to create a coroutine

5.1 Launch

launch {} is a coroutine builder to create a new coroutine. The launch function creates a registration process and returns it immediately. However, work continues in the background thread. It is often used when no return value is expected. For example, if we want to upload something to the server and we are not interested in the results

5.2 Async

async {} is a couroutine builder that creates a new coroutine and uses it in case of wanting to return a desired value:

async will return the deferred object. We call await () on that deferred object, so await will wait, pause the coroutine execution until the async finishes its computation and returns the value of the coroutine.

Summary

Now you will understand what coroutines are and their basic usage. We’ll learn more about these exception handling processes, scopes, and handling in my upcoming articles.

Share the news now

Source : Viblo