Handler vs RX vs Kotlin. An illustrative example of using coroutines


The idea of ​​writing this article came to me when I started to study coroutines, it was 2019, then I just entered Android development and I was interested in everything connected with it. At that time, coroutines were just gaining momentum and they began to talk about them at various conferences and meetups related to Android development, where I actually learned about them. At such meetups, they told how they are good, what are their advantages from streams, etc. It was then that I became interested in them, began to google about coroutines, read articles and watch videos on coroutines and look for some trainings on the Internet. However, then, and even now, I practically did not meet a single article where they show coroutines using a real example and explain it in simple language, so it was difficult for me to figure out how to apply them in a real task, for example, we can replace Handler with coroutines, but such examples in the global I could not find a web called the Internet. And then I had a thought in my head, but there would be such an article on the swag, how much time I would save on studying coroutines, I would see how to use them in action, I would understand how they are used in practice and the process of studying them would be much more efficient and faster, and I would use them faster in real projects. And so I wrote such an article and I hope you, dear reader, will find it useful and save your precious time for studying them.

Anyone, even a beginner, Android developer knows that the main thread (MainThread) of the application is responsible only for drawing the screen and rendering views. Other operations such as uploading data from the server, from the file system, database, etc. must be executed in a separate thread so as not to overload the main thread, speed up the application, avoid all kinds of crashes, etc. For these purposes, there are many ways such as coroutines, handler, AsyncTask, RX, etc. In this article, we we will not talk about deprecated methods such as, for example, AsyncTask, but consider only 3: coroutines, handler and RX.

I want to warn you right away that in this article I will not describe in detail how each of these methods works and I did not bother too much about the “cleanliness” of the code that I will show in the examples. I just want to show, with an example of one problem that every developer may face when creating their application, what a solution would look like using these three methods.

For demonstration, an application was developed that unloads the list of mail addresses from the contact list and displays them on the application screen. Well, let’s get started.

Handler

One of the most common methods of asynchronous work in Android applications is using a mechanism such as Handler.

Handler is a mechanism that allows you to work with a message queue. It is attached to a specific thread and works with its queue. On Android, a message queue can be bound to a thread. We can put messages there, and the system will monitor the queue and send messages for processing.

Let’s consider the implementation of this mechanism using an example:

First, let’s declare Handler in the activity lifecycle method onCreate () and override the function there handleMessage () and we will implement a message handler there that will serve as a trigger for the fact that the list of mail addresses is received from the database and we can fill the view element with them.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val handler = Handler(object : Callback() {
        fun handleMessage(msg: Message?): Boolean {
            if (MSG_EMAILS_ARE_LOADED === msg.what) {
                emailView.setAdapter(ArrayAdapter<Any?>(applicationContext,
                        android.R.layout.simple_dropdown_item_1line, getEmails())))
            }
            return true
        }
    })
}

Then you need to implement a separate function in which a stream will be created in which we will receive a list of mail addresses from the phone book of the smartphone and write them to the collection as soon as all mail addresses are received. MSG_EMAILS_ARE_LOADED will be sent to the Handler of the UI stream, where the view element will be filled with information received from contacts.

private fun loadEmailsFromContacts() {
    val loadContactsThread = Thread {
        loadEmailsFromContacts()
        handler.sendEmptyMessage(MSG_EMAILS_ARE_LOADED)
    }
    loadContactsThread.start()
}

Agree that the code does not look very readable, loading email addresses is implemented in one place, and their display on the application screen in another, and when this approach is implemented in a large project it will be difficult for a developer who will read the code (for example, during a code review) , immediately understand what’s what.

RX

Let’s now solve this problem with another equally well-known approach as RX. Here you will no longer need to purposefully declare something in the methods of the activity lifecycle. It will be enough just to call the function, in which all the magic will be implemented. Let’s implement this task.

private fun loadEmailsFromContacts() {
    Observable.create { list: ObservableEmitter<Any?> ->
        loadEmails()
        list.onNext(getEmails())
        list.onComplete()
    }.subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe { v: Any? ->
            emailsFromContacts = ArrayList(v as List<String>?)
                       emailView.setAdapter<ArrayAdapter<String>>(ArrayAdapter<String>(getApplicationContext(),      
                       R.layout.simple_dropdown_item_1line, emailsFromContacts))
            }
}

Here the code looks more compact. Loading and displaying a list of postal addresses is already in one place. The asynchronous code appears to be sequential, although loading email addresses and displaying them on the screen are performed on separate threads.

Coroutines

To write code using reactive programming, you need to understand why you need functions such as create {}, map, just and so on, what to implement inside them, what needs to be specified in subscribeOn ()and what in observeOn (), why do we need a Scheduler, what kinds of it there are and what should be implemented in subscribe {}… Well, in general, even though the asynchronous code looks like sequential, it has become more compact and easier to understand, the developer will still need to spend time to figure it out, understand what is being executed on which thread, and from the point of view of readability, it looks to put it mildly … And here coroutines come to our aid. Let’s see how this task will be implemented.

private fun loadImages() = CoroutineScope(Dispatchers.IO).launch {
    loadEmails()
    CoroutineScope(Dispatchers.Main).launch {
        emailView.setAdapter(ArrayAdapter<Any?>(applicationContext,
                android.R.layout.simple_dropdown_item_1line, getEmails()))
    }
}

Here the number of lines of code has been reduced, the asynchronous code looks like sequential, hiding all the complexity inside the libraries. The example shows that the code has become easier to understand and cleaner, and you can immediately see what is being executed and in which thread.

It is also worth noting that coroutines are “lightweight streams”, but they are not the streams that we know in Java, and unlike streams, you can create as many as you like, they are cheap to create, ie. the overhead is negligible compared to streams. It’s also worth saying that coroutines provide the ability to run asynchronous code without any blocking, which opens up more opportunities for creating applications. In simple words, calculations become interruptible if a block occurs, for example, due to waiting for a response to a request to obtain a list of mail addresses, the coroutine does not stand idle, but takes on another task for execution until the response is received.

Coroutines are a kind of add-on over threads, they run on threads (run in a thread pool) under the control of a library. Although from the point of view of users, coroutines behave in many ways like regular streams, but unlike native streams, they do not have such problems as deadlocks, starvation, etc.

Output

From the examples above, you have clearly seen that using coroutines makes the code easier to understand, asynchronous code visually looks like sequential. Coroutines are cheap to create, there are thousands of them to create, and will be easy to run. Coroutines allow you to avoid blocking threads, which allows you to implement interruptible computations.

If you look towards RX Java, then there are methods subscribeOn () and observeOn (), which indicate in which stream we receive data, and in which we display it in views. And when reading the code written in RX Java, the developer does not immediately become clear in which thread what is being executed, he needs time to understand all this. Another thing is coroutine where everything is clear and understandable at once, and you can see this in the examples I have given above. Now try to see examples of RX and coroutines in this vein.

Look again at the example written in RX, if you immediately understand which block of code is executed in the main thread, and which in “io”. And take a look at an example written in coroutines where everything is clear and obvious.

All these advantages indicate one thing that the use of coroutines is what a mobile developer needs when creating an application.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *