Kotlin Coroutines In Android Project

Evgeny Kabak, a developer at Axmor, shared his experience in testing Coroutines in one of the ongoing projects.
5 minute read

Coroutines Kotlin VS RxJava in async code

For those who are not familiar with Kotlin, it will be worth to briefly introduce Coroutines in particular. In May 2017, Google announced Kotlin as the official Android programming language, which confirms the relevance of studying Kotlin.

Axmor Android startup projects are written in Kotlin, allowing us to learn existing features and monitor new releases. When the Kotlin developers announced Coroutines as a new asynchronous programming tool, it became interesting to test them in production. Regarding specification, the declared functionalities are more advanced than existing solutions, enabling the team to achieve our goals easily.

So, what are Kotlin Coroutines used for? Coroutines can be used to download something from the web, retrieve data from the database or just perform long calculations that won't block the users interface.

Within the Android context in the async task threads, Coroutines can be safely considered as a rival of RxJava.. Despite the fact that RxJava functionalities are much richer (it is quite a massive library with its own approach and philosophy), it is more convenient to develop with Coroutines as it is just a part of a programming language. The tasks implemented on RxJava with operators (library methods), are resolved much easier with the built-in Coroutines language tools. Additionally, in RxJava, you are required to know and understand how library operators work in order to choose and apply them correctly. Of course, it is also necessary to know the language features in order to apply them but when it comes to reducing development time, it is worth considering how relevant and time consuming it is to study the libraries’ functionalities in order to solve small tasks, compared to learning the language in which the entire project is written.

Kotlin Coroutines Implementation Examples

Coroutines support is built in Kotlin, but all classes and interfaces are located in a separate library. In order to use them it is necessary to add a gradle dependency:

dependencies {
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.1'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1'
}

Example:

val job: Job = GlobalScope.launch(Dispatchers.IO) {
    longRunningMethod()
}

Let's figure out what is happening here:

longRunningMethod() — method that must be performed asynchronously.

GlobalScope is a lifetime framework for Coroutine instance. The Coroutine instance will exist as long as the application in which it is running, is active. GlobalScope extends the Coroutinescope interface. You can implement your scope function, for example, in the Activity, and this will lead to the fact, that in the Activity, running Coroutine will be automatically cancelled if the Activity is completed or crashed.

launch — a method for Coroutine asynchronous launch. Accordingly, the longRunningMethod() method will start immediately. The method returns an instance of the Job class. This object can be used, for example, to cancel Coroutine — job.cancel(). Alternative — method asunc() It will return a Deferred — deferred Coroutine, which can be launched later.

Dispatchers.IO — is one of the parameters of the launch() method, which displays the created Coroutine dispatcher. Dispatchers.IO is specifically used for non-blocking background tasks of the main thread. If you specify Dispatchers.Main, then the Coroutine will be running in the main thread.

As a result, we have a simple method for launching asynchronous code. In this small piece of code there are hidden advantages that are not visible upon first glance:

  • Coroutines are lightweight. A similar code which will create and run the thread, will require a lot more memory:
thread {
    longRunningMethod()
}

We can create thousand instances of Coroutines;

  • Coroutines can be suspended. . The delay(timeout) method will suspend the execution of Coroutines, but this will not affect the thread in which it runs;
  • contrary to RxJava, to write async code, you do not need to learn a lot of operators like merge, zip, andThen map, flatMap, etc. Code that will be run asynchronously, can be simply written using minimum additional methods. To implement more complex logic, familiar language patterns, such as foreach, repeat, filter, etc can be used.

Asynchronous Programming

Let's return to our task of Coroutines using. The Android application which we are working on at the moment, communicates with the server and stores data in the database. At first glance, in terms of functionality, there is nothing new, however, since we are testing Kotlin Coroutines, a new tool for async code development, the implementation approach itself is quite interesting.

In our app we implement asynchronous code. But why? This is due to the fact that data exchange between the server and database are quite time consuming operations. Whilst a single task is running, it is possible to run several operations at once, without blocking the main thread, asynchronous approach in action. In synchronous programming, operations are run sequentially, meaning that the each command is executed only upon completion of the previous one, and if one of them runs too long, the application may freeze. It is commonly understood that application freezing is highly disliked by users, nevertheless, it can be found in some solutions. As a reminder, we solve tasks using asynchronous code.

Kotlin Coroutines Project Implementation

As fact, launching an abstract async code is good, but we will try to solve a more applicable task. Suppose a request must be made to the server in order to retrieve data and reflect the results. Let's see how this can be achieved with the help of Coroutines. For the sake of simplicity, we will perform the download in ViewModel and communicate with the Activity using LiveData.

Instantiate an inheritance class ViewModel:

class LoginViewModel(application: Application) : BaseViewModel(application) {
    private val loginLiveData = MutableLiveData<Resource<UserProfile>>()

        fun getLoginLiveData(): LiveData<Resource<UserProfile>> {
        return loginLiveData
    }

    fun login(name: String, password: String) {
        runCoroutine(loginLiveData) {
            val response = ServerApi.restApi.authorize(name, password).execute()
            return@runCoroutine response.body()!!
        }
    }
}

Inside, the model contains MutableLiveData with user data, which we receive after authorization. Protected LiveData is given to the outside, ensuring that no one except the ViewModel can modify the data inside. The user profile is wrapped in a Resource<> class, a utility class for the convenience of transfer process status to the View:

data class Resource<T>(
    val status: Status,
    val data: T?,
    val exception: Exception?
) {
    enum class Status {
        LOADING,
        COMPLETED
    }
}

As demonstrated above, in the View, process status data can be transmitted to alert about the download completion, and if so, then with a return code of error or success.

Coroutines are run in the login(). method. It calls the base class' runCoroutine() method:

protected fun <T> runCoroutine(correspondenceLiveData: MutableLiveData<Resource<T>>, block: suspend () -> T) {
    correspondenceLiveData.value = Resource(Resource.Status.LOADING, null, null)

    GlobalScope.launch(Dispatchers.IO) {
        try {
            val result = block()
            correspondenceLiveData.postValue(Resource(Resource.Status.COMPLETED, result, null))
        } catch (exception: Exception) {
            val error = ErrorConverter.convertError(exception)
            correspondenceLiveData.postValue(Resource(Resource.Status.COMPLETED, null, error))
        }
    }
}

This method has two parameters. The first one is a typical instance of LiveData where the data will be written. The second parameter is a code, which needs to be executed asynchronously. In the login() method, we transfer a code that sends the authorization data to the server and receives a user profile.

How it works as a module:: View receives the LiveData from ViewModel and subscribes to its changes. There are three types of changes — some process is in progress, everything ends with an error, everything ends successfully. At the right time, the login() method is called. In sequential order: writing process status data to LiveData, a server request, receiving of data, writing received data to LiveData. Or errors, if the server request failed.

Conclusions

Inevitably, it is impossible to cover all aspects of the asynchronous programming new tool in one article. For those who are interested in Kotlin Coroutines development, the best advice is to learn and test by combining Coroutines, channels, the implementation of the Actor model and other features.

Despite the fact that the example given displays a rather trivial task — downloading data from the server, which is a process that can be complete in almost every application, it illustrates the principle of working with Coroutines. Apart from downloading, any other task could be accomplished, the code snippets simply demonstrates how a Coroutine can easily be used to wrap any operation.

As a result, it is evident that asynchronous programming for Android is easier to implement with the support of Coroutines:faster development, easy code readability. There of course are some risks: it takes some time to learn Kotlin Coroutines and their capabilities may not cover the solution requirements. Therefore, it is recommended that you carefully read the area of their application before implementing Coroutines into your project.

Kotlin developers official website: http://kotlinlang.org/

Announce release of Kotlin 1.3.0 with Coroutines: https://blog.jetbrains.com/kotlin/2018/10/kotlin-1-3/


Published 07 February 2020
Author Evgeny Kabak