Imagine that you need to do some long time operation and update UI with its result. You probably know that you should not call things that might take some time on UI thread. If you do so, an application might become unresponsive and that would lead to poor user experience or system stepping in and offering to kill an application with infamous Application is not responding dialog.
So how can you avoid this? There are many ways. Most commonly used are threads, AsyncTasks and now also Kotlin coroutines. In this article, I will make a quick introduction and provide reference links for more details.
Thread
Thread is most simple of these and is pure Java.
You can create a thread like this:
new Thread(new Runnable() { @Override public void run() { // code to run long operation } }).start();
You create Thread object, pass a Runnable object and start it. Simple. But things get complicated if you want to update UI with operation results. UI can only be updated from UI thread and attempt to do so will lead to CalledFromWrongThreadException. This can be solved by posting UI changes back to UI thread but the code will be pretty ugly:
new Thread(new Runnable() { @Override public void run() { // code to run long operation new Handler().post(new Runnable() { @Override public void run() { textView.setText(result); } }); } }).start();
Async Task
AsyncTask is designed to be helper class around Thread and Handler. AsyncTask is abstract class and must be subclassed. AsyncTask has 4 steps: onPreExecute, doInBackground, onProgressUpdate and onPostExecute. DoInBackground must be overriden. To use AsyncTask 3 generic types must be provided that are called Params, Progress and Result. If you don’t want some of these method to return object or have object as a parameter, you can pass Void.
A picture is worth a thousand words so let’s look at the commented example:
public class DownloadFileTask extends AsyncTask<String, Double, Boolean> { @Override protected void onPreExecute() { // called before download on UI thread showLoader(); } @Override protected Boolean doInBackground(String... params) { // called on UI thread String url = params[0]; Download download = startDownload(url); while (download.isDownloading) { publishProgress(download.getProgress()); } return download.isSuccessful(); } @Override protected void onProgressUpdate(Double... percent) { // updates UI while doInBackground is processed, triggered by publishProgress setProgress(percent); } @Override protected void onPostExecute(Boolean isSuccessful) { // called after doInBackground hideLoader(); showToast(isSuccessful); } }
And task can be called this way
DownloadFileTask().execute(url)
You don’t have to override all methods but still it is lot of code. There is another disadvantage. Starting with API 11, Async tasks are executed serially on single background thread. If you need parallel execution, you can use executeOnExecutor but it is not without problems.
Kotlin Coroutines
With Kotlin there is a new way how to write asynchronous code that will look like normal sequential code. While Java threads are hard to read, Kotlin coroutines hides complicated logic inside library functions. Yes, library. Kotlin coroutines are not (yet) part of the language itself but are an experimental library. I will show you how to add it to project later.
Coroutines have many advantages. They are simple to read, unlike thread they are lightweight and unlike AsyncTask lot of them can run at same time.
Let’s look at some code. For example, a function that counts Fibonacci sequence.
private suspend fun count(howMany: Int): LongArray { val array = LongArray(howMany) for (i in 0 until howMany) { if (i < 2) { array[i] = i.toLong() } else { array[i] = array[i - 2] + array[i - 1] } } return array } launch() { val fibonacchiNumbers = count() // do something with numbers }
Here we have suspending function with keyword suspend. These functions can be suspended at any point and can only be called inside coroutines. So inside coroutine we can call suspended function, wait for it to finish and do something with the result.
We cannot update UI from the coroutine. To do this, we can use async command.
private fun countFibonachiAsync(howMany: Int) : Deferred<LongArray> = async { val array = LongArray(howMany) for (i in 0 until howMany) { if (i < 2) { array[i] = i.toLong() } else { array[i] = array[i - 2] + array[i - 1] } } array } async { val array = countFibonachiAsync(10).await() result.text = array.joinToString() }
Async enables us to return values from coroutine. Instead of suspended function we have Deferred functions with the result. We call this function and wait for its result. Then we can update UI. No more callbacks!
If you want to try, add this dependency to your project:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.22.2'