Android Basics: Kotlin(2) - Coroutine

1. What is a coroutine?

1-1 How it different from a Thread?

  • Executed inside of a thread.
  • Suspendable
  • Can easily change their context (the thread they're running in)

2. Launch a coroutine

2-1 Global scope

  • Live as long as the application does. Will be destroyed after the execution is done.
1
2
3
4
5
GlobalScope.launch {
  delay(3000L) 
  //Only sleep the coroutine but not the whole thread. But when the thread itself is terminated,   
  //the coroutine will be canceled as well.
}

2-2. Suspend function

  • A suspend function can only be called in another suspend function or inside of a coroutine.
  • Use withContext to switch to another thread
1
2
3
4
5
6
7
8
9
GlobalScope.launch {
  delay01() 
  delay01()
  print("something") //will reach here after 6 sec.
  withContext(Dispatchers.main) {
    textView.text = "result" //will go back to main thread
  }
}
suspend fun delay01() = delay(3000L)
  • Use runBlocking to start a coroutine in the main thread
    • Different from using GlobalScope.launch(Dispatchers.main), which won't block the main thread whereas runBlocking will.
    • Use cases
      • Want to simply call suspend function from the main thread but don't care about the UI behavior
      • Call suspend function in junit test
      • debug a coroutine (set logs before and after the execution)
1
2
3
4
GlobalScope.launch(Dispatchers.main) { } //won't block the main thread
runBlocking {
  launch(Dispatchers.IO) { } //Can start new coroutines inside, asynchronously
 } //block the main thread

2-3. Jobs, Waiting, Cancellation

  • Launch a coroutine will actually return a Job
  • Use Job.cancel() to cancel a job.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
val job = GlobalScope.launch(Dispatchers.Default) {
  if (isActive) {
    //Do some job
  }
}

runBlocking {
  delay(2000L)
  job.cancel() //Cancel the job after 2 sec.
}

Equivalent to:

1
2
3
4
5
6
7
GlobalScope.launch(Dispatchers.Default) {
  withTimeout(2000L) {
    if (isActive) {
    //Do some job
    }
  }
}

2-4. Async and Await

  • functions inside of a coroutine are executed synchronously by default
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
GlobalScope.launch(Dispatchers.IO) {
  val time = measureTimeMillis{
      val answer1: String? = null
      val answer2: String? = null
      launch { answer1 = suspendFunction1() }
      launch { answer2 = suspendFunction2() }
      Log.d(TAG, answer1.await())
      Log.d(TAG, answer2.await())
  }
  Lod.d(TAG, "Execution time is: $time") //About 6 sec.
}
suspend fun suspendFunction1(): String {
  delay(3000L)
}
suspend fun suspendFunction2(): String {
  delay(3000L)
}
  • async function will get a Deferred result of type String(in this case)
  • await function will block the coroutine until the result is returned
  • use measureTimeMillis{ } to measure the duration of time
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
GlobalScope.launch(Dispatchers.IO) {
  val time = measureTimeMillis{
      val answer1 = async { suspendFunction1() }
      val answer2 = async { suspendFunction2() }
      Log.d(TAG, answer1.await())
      Log.d(TAG, answer2.await())
  }
  Lod.d(TAG, "Execution time is: $time") //About 3 sec.
}

suspend fun suspendFunction1(): String {
  delay(3000L)
}
suspend fun suspendFunction2(): String {
  delay(3000L)
}

2-5. lifecycleScope & viewModelScope

  • Add dependencies
1
2
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$arch_version"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$arch_version"
  • lifecycleScope
    • Make a coroutine stick with the current Activity/Fragment's lifecycle. Ensure the coroutine is destroyed if the holder is destroyed.
  • viewModelScope
    • Make a coroutine stick with the current viewModel lifecycle.