I just started learning Kotlin coroutines and was trying to simulate some long time API-calls with showing the result on the UI:
class MainActivity : AppCompatActivity() {
fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
override
fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.setContentView(R.layout.activity_main)
val resultTV = findViewById(R.id.text) as TextView
val a = async(CommonPool) {
delay(1_000L)
6
}
val b = async(CommonPool) {
delay(1_000L)
7
}
launch(< NEED UI thread here >) {
val aVal = a.await()
val bVal = b.await()
resultTV.setText((aVal * bVal).toString())
}
}
}
I don't understand how could I possibly use launch
method with main
context.
Unfortunately, I was not able to find anything about delivering results for some specific threads on the official tutorial for coroutines.
There are a couple of useful tools that can be used for the purpose of long time running API-calls in Activity
/Fragment
. So basically if you want to run two long running tasks in parallel and update UI after both are finished you can do it the next way:
lifecycleScope.launch {
// launching two tasks in parallel
val aValDeferred = executeLongRunningTask1Async()
val bValDeferred = executeLongRunningTask2Async()
// wait for both of them are finished
val aVal = aValDeferred.await()
val bVal = bValDeferred.await()
// update UI
resultTV.setText((aVal * bVal).toString())
}
private fun executeLongRunningTask1Async(): Deferred<Int> = lifecycleScope.async(Dispatchers.Default) {
delay(1_000L)
6
}
private fun executeLongRunningTask2Async(): Deferred<Int> = lifecycleScope.async(Dispatchers.Default) {
delay(1_000L)
7
}
lifecycleScope
- is a CoroutineScope
, by default it has Dispatchers.Main
context, it means we can update UI in the launch
block. For LifecycleScope
, use androidx.lifecycle:lifecycle-runtime-ktx:2.4.0
or higher.
lifecycleScope.async(Dispatchers.Default)
- here Dispatchers.Default
is used as a context of the coroutine to have the async
block running in the background thread.
Edit:
Also see an official example in Kotlin repo
you need to implement Continuation interface which makes a callback onto Android UI thread and Coroutine context
e.g. (from here)
private class AndroidContinuation<T>(val cont: Continuation<T>) : Continuation<T> by cont {
override fun resume(value: T) {
if (Looper.myLooper() == Looper.getMainLooper()) cont.resume(value)
else Handler(Looper.getMainLooper()).post { cont.resume(value) }
}
override fun resumeWithException(exception: Throwable) {
if (Looper.myLooper() == Looper.getMainLooper()) cont.resumeWithException(exception)
else Handler(Looper.getMainLooper()).post { cont.resumeWithException(exception) }
}
}
object Android : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
AndroidContinuation(continuation)
}
Then try:
launch(Android) {
val aVal = a.await()
val bVal = b.await()
resultTV.setText((aVal * bVal).toString())
}
more info:
https://medium.com/@macastiblancot/android-coroutines-getting-rid-of-runonuithread-and-callbacks-cleaner-thread-handling-and-more-234c0a9bd8eb#.r2buf5e6h
You shall replace < NEED UI thread here >
in your code with UI
context from kotlinx-coroutines-android
module of kotlinx.coroutines project. Its usage is explained in the Guide to UI programming with coroutines with quite a few examples.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With