Android Kotlin Coroutines: Basic & Usage

This Story is from the Kotlin- Series.For begginer please follow my previous link for coroutines Stepup.This article is a brief summary of official Kotlin doc and a few other resources that I have gone through over time during my learning.

What are Coroutines?

A coroutine is an instance of suspendable computation, conceptually similar to a thread, in the sense that it takes a block of code to run and has a similar life-cycle, it is created and started, but it is not bound to any particular thread.On Android, coroutines help to manage long-running tasks that might otherwise block the main thread and cause your app to become unresponsive. Over 50% of professional developers who use coroutines have reported seeing increased productivity.

Coroutines are lightweight threads and they are so cheap to create which open the doors to asynchronous programming for developers. This means we can call more than one functions asynchronously and get the result in a shorter duration in very effective way. Now imagine how faster our validations would be as we can call them asynchronously. All those things (API calls, Database access…) which need a background thread to process are just a few lines away than creating separate worker threads for it and then managing it. And making coroutines life-cycle aware is so simple.

suspend fun validateEntries(str: String): Boolean = coroutineScope {
val deferred1 = async { printDelayed(str) }
val deferred2 = async { printDelayed(str) }
deferred1.await() && deferred2.await()
}

CoroutineBuilder

A function that takes some suspending lambda (lambda marked with suspend modifier) as an argument, creates a coroutine, and, optionally, gives access to its result in some form.

Coroutine builders provided by kotlinx.coroutines:
# launch {}: Launch a coroutine that does not have any result, it returns Job.
# async{}: Returns a single value result using Deffered- a light-weight non-blocking future that represents a promise to provide a result later. So, we can use .await() on a deferred value to get its eventual result, but Deferred is also a Job, so we can cancel it if needed.
# runBlocking{}: Blocks current thread until the execution of coroutine.

# delay: is a special suspending function that does not block a thread but suspends coroutine.

Examples overview

The examples in this topic make a network request and return the result to the main thread, where the app can then display the result to the user.

ViewModel includes a set of KTX extensions that work directly with coroutines. These extension are lifecycle-viewmodel-ktx library and are used in this guide.

First, let’s take a look at our Repository class and see how it's making the network request:

sealed class Result<out R> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
}

class LoginRepository(private val responseParser: LoginResponseParser) {
private const val loginUrl = "https://example.com/login"

// Function that makes the network request, blocking the current thread
fun makeLoginRequest(jsonBody: String): Result<LoginResponse> {
val url = URL(loginUrl)
(url.openConnection() as? HttpURLConnection)?.run {
requestMethod = "POST"
setRequestProperty("Content-Type", "application/json; utf-8")
setRequestProperty("Accept", "application/json")
doOutput = true
outputStream.write(jsonBody.toByteArray())
return Result.Success(responseParser.parse(inputStream))
}
return Result.Error(Exception("Cannot open HttpURLConnection"))
}
}

makeLoginRequest is synchronous and blocks the calling thread. To model the response of the network request, we have our own Result class.

The ViewModel triggers the network request when the user clicks, for example, on a button:

class LoginViewModel(private val loginRepository: LoginRepository): ViewModel() {

fun login(username: String, token: String) {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
loginRepository.makeLoginRequest(jsonBody)
}
}

With the previous code, LoginViewModel is blocking the UI thread when making the network request. The simplest solution to move the execution off the main thread is to create a new coroutine and execute the network request on an I/O thread:

class LoginViewModel(private val loginRepository: LoginRepository): ViewModel() {

fun login(username: String, token: String) {
// Create a new coroutine to move the execution off the UI thread
viewModelScope.launch(Dispatchers.IO) {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
loginRepository.makeLoginRequest(jsonBody)
}
}
}
  • viewModelScope is a predefined CoroutineScope that is included with the ViewModel KTX extensions. Note that all coroutines must run in a scope. A CoroutineScope manages one or more related coroutines.
  • launch is a function that creates a coroutine and dispatches the execution of its function body to the corresponding dispatcher.
  • Dispatchers.IO indicates that this coroutine should be executed on a thread reserved for I/O operations.

Since this coroutine is started with viewModelScope, it is executed in the scope of the ViewModel. If the ViewModel is destroyed because the user is navigating away from the screen, viewModelScope is automatically cancelled, and all running coroutines are canceled as well.One issue with the previous example is that anything calling makeLoginRequest needs to remember to explicitly move the execution off the main thread.

Let's see how we can modify the Repository to solve this problem for us.

class LoginRepository(...) {
suspend fun makeLoginRequest(jsonBody: String):Result<LoginResponse> {
// Move the execution of the coroutine to the I/O dispatcher
return withContext(Dispatchers.IO) {
// Blocking network request code
}
}
}

withContext(Dispatchers.IO) moves the execution of the coroutine to an I/O thread, making our calling function main-safe and enabling the UI to update as needed.makeLoginRequest is also marked with the suspend keyword. This keyword is Kotlin's way to enforce a function to be called from within a coroutine.

The login function now executes as follows:

  • The app calls the login() function from the View layer on the main thread.
  • launch creates a new coroutine to make the network request on the main thread, and the coroutine begins execution.
  • Within the coroutine, the call to loginRepository.makeLoginRequest() now suspends further execution of the coroutine until the withContext block in makeLoginRequest() finishes running.
  • Once the withContext block finishes, the coroutine in login() resumes execution on the main thread with the result of the network request.

Handling exceptions

To handle exceptions that the Repository layer can throw, use Kotlin's built-in support for exceptions. In the following example, we use a try-catch block:

class LoginViewModel(private val loginRepository: LoginRepository
): ViewModel() {

fun makeLoginRequest(username: String, token: String) {
viewModelScope.launch {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
val result = try {
loginRepository.makeLoginRequest(jsonBody)
} catch(e: Exception) {
Result.Error(Exception("Network request failed"))
}
when (result) {
is Result.Success<LoginResponse> -> // Happy path
else -> // Show error in UI
}
}
}
}

In this example, any unexpected exception thrown by the makeLoginRequest() call is handled as an error in the UI.

If you liked the article, clap clap clap 👏👏👏 as many times as you can.

LIKED SO MUCH! Medium allows up to 50 claps.

Senior Android Developer. Working on technology Like Java,Kotlin, JavaScript.Exploring Block Chain technology in simple words.