Kotlin Coroutines Basics - Simple Android App Demo
Tags: kotlin, beginners, android, programming
Published: 2022-04-15 23:44:06

**This simple Android app demonstrates the basic Kotlin coroutines usages such as creating coroutines (launch and async), cancelling coroutines.**

This is part of the Kotlin coroutines series:
* **Part 1 - Kotlin Coroutines Basics - Simple Android App Demo**    
* [Part 2 - kotlinx.coroutines.delay() vs Thread.sleep()](https://vtsen.hashnode.dev/kotlinxcoroutinesdelay-vs-threadsleep)    
* [Part 3 - GlobalScope vs viewModelScope vs lifecycleScope vs rememberCoroutineScope](https://vtsen.hashnode.dev/globalscope-vs-viewmodelscope-vs-lifecyclescope-vs-remembercoroutinescope)    
* [Part 4 - launchWhenCreated() vs launchWhenStarted() vs launchWhenResumed() vs repeatOnLifeCycle()](https://vtsen.hashnode.dev/launchwhencreated-vs-launchwhenstarted-vs-launchwhenresumed-vs-repeatonlifecycle)

I created this simple Android app to help me to understand the basic usage of Kotlin coroutines.  The app demonstrates how to create coroutines jobs and run them concurrently.  It probably won't cover 100% of the use cases, maybe at least 90%?

The app also uses simple [MVVM architecture](https://vtsen.hashnode.dev/mvc-vs-mvp-vs-mvvm-design-patterns) (without the `Model` to be exact). 

## App Overview

![Kotlin_Coroutines_Basics_Simple_Android_App_Demo_01.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1643267063515/37hySmZj4.gif)

There are 3 buttons and 2 display texts (left and right) UI in this app. 

- **Launch** and **Async** buttons update both **left text** and **right text** UI concurrently
- **left text** and **right text** are started at **-1** as an invalid value. 
- When **Launch** or **Async** button is clicked, coroutines are created to update **left text** UI from **0** → **9** and **right text** UI from **10** → **19**
- If the **Cancel** button is clicked, both texts are stopped being updated and value is set to **-1**.
- If no **Cancel** button is clicked, both texts continue to be updated until the final value **9** and **19**

There are 2 ways to create coroutines:
- `CoroutineScope.launch` 
- `CoroutineScope.async` 


## CoroutineScope.launch

To create a coroutine, we need to create `CoroutineScope` first. In `ViewModel`, `CorotineScope` is already created (i.e. `viewModelScope`). So it is highly recommended to use it instead of creating it yourself.  One benefit is, all the `CoroutineScope` children will be automatically cancelled when `ViewModel` is destroyed. 

```kotlin
// Create new coroutine - current job is the parent job
currentJob = viewModelScope.launch {

	// Create a first new sub-coroutine - job1 is the child job
	val job1 = launch {
		...
	}
	
	// Create a second new sub-coroutine - job2 is the child job
	val job2 = launch {
		...
	}

	// job1 and job2 are coroutines that run concurrently
	
	// wait for both job1 and job2 to complete
	job1.join()
	job2.join()
	...	
}
```
`CoroutineScpoe.launch` is a non-blocking function, and it returns `Job` immediately.  To achieve concurrency, we can call `CoroutineScpoe.launch` multiple times within the same coroutine scope.  `job1` is responsible to update the **left text** UI and `job2` is responsible to update the **right text** UI.

If you want to wait for the job to complete, you need to call the `Job.join()` suspend function. This will wait until the job is completed before it moves to the next line.

## CoroutineScope.async

For creating coroutine that we want to wait for it's returned value, we use `CoroutineScope.async`. 

Similar to `CoroutineScpoe.launch`, `CoroutineScope.async` is a non-blocking function. Instead of returning `Job`, it returns `Deferred<T>`.  The last line in the `async block`, is the return type `T`. For example, `getData()` returns `Int`, thus, the `T` is `Int` type.

```kotlin
// Create new coroutine
viewModelScope.launch {
	// Create a sub-coroutine with async
	val deferred = async {  
		...
		getData()  
	}  
	// wait for async to return it's value
	data.value = deferred.await()
	...
}
```
Instead of using `Job.join()`,  you call `Deferred<T>.awailt()` to wait for the `CoroutineScope.async` to finish and also return the value from `getData()`.

## CoroutineScope.withContext()
By default, the coroutines are run on main/UI thread.  You should move the long-running tasks to different thread so that it doesn't block the main/UI thread.

To switch to a different thread, you specify `CoroutineDispatcher`.  Here are the common pre-defined `CoroutineDispatcher` that we can use:
- `Dispatchers.Main` - main/UI thread
- `Dispatchers.Default` - CPU operation thread
- `Dispatchers.IO` - IO or network operation thread

To use your own thread, you can create a new thread / new `CoroutineDispatcher` using `newSingleThreadContext("MyOwnThread")`. Most of the time, the pre-defined `CoroutineDispatcher` are enough.

When creating a coroutine either with `launch` or `async`, you can specify the `CoroutineDispatcher`.

```kotlin
viewModelScope.launch {
	// Create coroutine that runs on Dispatchers.Default thread 
	launch(Dispatchers.Default) {
		loadData()
	}
	// Create coroutine that runs on Dispatchers.Default thread 
	async(Dispatchers.Default) {
		loadData()
	}
}
```
However, a better solution is to use `CoroutineScope.withContext()` in the suspend function instead of specifying the `CoroutineDispatcher` during coroutine creation. This is recommended because it makes the suspend function safe to be called from main/UI thread.

```kotlin
private suspend fun loadData() {
	//Switches / moves the coroutine to different thread
	withContext(Dispatchers.Default) {
		...
	}
}
```
> Please note `CoroutineScope.withContext()` does NOT create a new coroutine. It moves the coroutines to a different thread. 


## Job.cancelAndJoin()
To cancel a coroutine job, we call `Job.cancel()` and `Job.join()`. Most of the time, you can just simply call `Job.cancelAndJoin()`.  Please note that `Job.cancelAndJoin()` is a suspend function. So you need to call it inside the coroutine.
 
```kotlin
fun onCancelButtonClick() {
	if (currentJob == null) return
	viewModelScope.launch() {
		currentJob!!.cancelAndJoin()
	}
}
```
 > `currentJob` is an existing coroutine job that was created before.

## kotlinx.coroutines.yield()
One important thing to note is **coroutine cancellation is cooperative**.  If a coroutine is non-cooperative cancellation, there is no way we can cancel it.  The coroutine will continue to runs until it is complete although `Job.cancel()` has been called.

To make a coroutine cancellation cooperative, you can use:
- `CoroutineScope.isActive`
- `kotlinx.coroutines.yield()`

`CoroutineScope.isActive` required `CoroutineScope` object to be called, and you need to add logic to exit the coroutine, thus it is less flexible.  Since `yield()` can be called in any suspend function, I personally prefer to use it.

> Please note the `kotlinx.coroutines.delay()` also make the coroutine cancellation cooperative.

For example, if you have a long-running task like below, the coroutine will not honor any `Job.cancel()` request.
```kotlin
private suspend fun simulateLongRunningTask() {  
    repeat(1_000_000) {  
	    Thread.sleep(100)  
    }  
}
```
To make it to accept the `Job.cancel()` request, you just need to add `yield()`.
```kotlin
private suspend fun simulateLongRunningTask() {  
    repeat(1_000_000) {  
	    Thread.sleep(100)  
	    yield()
    }  
}
```

## kotlinx.coroutines.JobCancellationException

When a coroutine is cancellation is accepted, an `kotlinx.coroutines.JobCancellationException` exception will be thrown.  You can catch the exception and perform some clean up.

```kotlin
currentJob = viewModelScope.launch {
	try {
		val job1 = launch {
			...
		}
		
		val job2 = launch {
			...
		}
		
		job1.join()
		job2.join()
		
	} catch (e: Exception) {
		// clean up here
		currentJob = null
    }	
}
```

## kotlinx.coroutines.coroutineContext
For debugging coroutine, logging is the easiest way. `kotlinx.coroutines.coroutineContext` is very useful for logging. It provides the coroutine and thread information. 

> Please note that it is a suspend property which can only be called from the suspend function.

Example of `Utils.log()` utility suspend function to wrap the `.Log.d()`:
```kotlin
object Utils {  
    suspend fun log(tag: String, msg: String) {  
        Log.d(tag, "$coroutineContext: $msg")  
    }  
}

//Usage
Utils.log("ViewModel", "======= Created launch coroutine - onButtonClick() =======")
```

Example of Logcat output:
```
D/ViewModel: [StandaloneCoroutine{Active}@5261b32, Dispatchers.Main.immediate]: ======= Created launch coroutine - onButtonClick() =======
```

## Some Thoughts
So far, all my [personal projects](https://vtsen.hashnode.dev/projects) do not use all the coroutines use cases above. I only use `CoroutineScope.launch` and `CoroutineScope.withContext()` which is enough for me to accomplish what I want. I don't even need to cancel a coroutine, although I can if I want to, and the apps still work perfectly. 

**[Update: April 13, 2022]:** I did use `joinAll()` parallelize a few network calls instead of running the code in sequential. Example below:

```kotlin
private suspend fun fetchArticlesFeed() : List<ArticleFeed> = coroutineScope {
    val results = mutableListOf<ArticleFeed>()
    val jobs = mutableListOf<Job>()

    for(url in urls) {
        val job = launch {
            val xmlString = webService.getXMlString(url)
            val articleFeeds = FeedParser().parse(xmlString)
            results.addAll(articleFeeds)
        }

        jobs.add(job)
    }

    jobs.joinAll()

    return@coroutineScope results
}
``` 

## Source Code
GitHub Repository: [Demo_CoroutinesBasics](https://github.com/vinchamp77/Demo_CoroutinesBasics)

---

*Originally published at [https://vtsen.hashnode.dev](https://vtsen.hashnode.dev/kotlin-coroutines-basics-simple-android-app-demo)*.