How to wait for end of a coroutine

Shkum picture Shkum · Dec 26, 2019 · Viewed 31.1k times · Source

I have some code below. Delay (3000) is just replacement for a long loop (or cycle). I’m expecting that after completion of loop println(res) will print “Some String” and then enable button. But in real life println(res) prints an empty string and button became enabled at same time when I click it. My question is: how I can wait for end of a coroutine and only after completion of the coroutine run println(res) and button.isEnabled = true.

private var res: String = ""

private suspend fun test(): String {
    delay(3000) // delay - just replacement for long loop
    return "Some String" // String received after loop
}

fun onClick(view: View) {
    res = ""
    button.isEnabled = false
    GlobalScope.launch {
        res = withContext(Dispatchers.Default) {
            test()
        }
    }
    println(res) // 1. trying to get string received after loop, but not working
    button.isEnabled = true // 2. button must be enabled after loop in cycle, but it's not waiting till end of loop
}

Answer

daneejela picture daneejela · Dec 27, 2019

The main thing to understand here is that code within coroutine is by default executed sequentially. I.e. coroutine is executed asynchronously in relation to "sibling" code, but code within coroutine executes synchronously by default.

For example:

fun DoSometing () { 

coroutineA {
doSomethingA1()
doSomethingA2()
}

some additional code 

}

Corroutine A will execute async in relation to some additional code but doSometingA2 will be executed after doSomethingA1 is done.

That means, that within a coroutine every next piece of code will be executed after the previous one is done. So, whatever you want to execute when coroutine is done, you just put at the end of that coroutine and declare context (withContext) in which you want to execute it.

The exception is of course if you start another async piece of code within coroutine (like another coroutine).

EDIT: If you need to update UI from the coroutine, you should execute that on the main context, i.e. you'll have something like this:

GlobalScope.launch (Dispatchers.IO) {

   //do some background work
   ...
   withContext (Dispatchers.Main) { 
       //update the UI 
       button.isEnabled=true  
       ...
     }
}