I'm trying to use the latest coroutines in 0.30.0, and having trouble figuring out how to use the new scoping. In the original coroutines I could set the context with UI or CommonPool and everything worked correctly.
Now I'm trying to use the GlobalScope in my ViewModel while reading from a room database, and then I want to assign the value returned to my LiveData object.
I'm getting the following error when I try to set the LiveData value
java.lang.IllegalStateException: Cannot invoke setValue on a background thread
fun getContact() {
GlobalScope.launch {
val contact = contacts.getContact() // suspended function
withContext(Dispatchers.Default) { phoneContact.value = contact }
}
}
I only see Default, Unconfined and IO for dispatchers, and none of them work, I can't figure out what I'm doing wrong? Where is my option for the Main Thread?
We can for example schedule coroutines on a Java Executor or on Android main looper. However, we can't schedule coroutines on just any thread, it has to cooperate.
Quoting definition of Global Scope from Kotlin's documentation– “Global scope is used to launch top-level coroutines which are operating on the whole application lifetime and are not cancelled prematurely.” GlobalScope creates global coroutines and these coroutines are not children of some specific scope.
Where to Specify the Dispatcher. First, you should be aware of the dispatcher specified in the coroutine scope you're using. GlobalScope doesn't specify any, so the general default is in effect, the Default dispatcher.
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.
A global CoroutineScope not bound to any job. Global scope is used to launch top-level coroutines which are operating on the whole application lifetime and are not cancelled prematurely. Active coroutines launched in GlobalScope do not keep the process alive. They are like daemon threads. This is a delicate API.
Here's a sample adapted from the documentation on CoroutineScope, it shows how to use your activity as the coroutine scope: class MyActivity : AppCompatActivity (), CoroutineScope { // Sets up the default dispatcher and the root job that we can use to centrally // cancel all coroutines.
Dependency info. To use coroutines in your Android project, add the following dependency to your app's build.gradle file: dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' } Executing in a background thread. Making a network request on the main thread causes it to wait, or block, until it receives a response.
You solved your immediate problem by adding the dependency, but let me add a note on your usage of GlobalScope
.
Using the GlobalScope
in production code is an antipattern. It's there for similar reasons like
runBlocking
, to make it easy to do quick experiments. You should especially avoid it on Android due to the complicated lifecycle of app components.
If you're launching a coroutine from an Android event handler, you should use the current Activity as its coroutine scope. This will ensure your coroutine gets canceled when the activity gets destroyed. Without that the coroutine will go on, referring to the now-dead activity.
Here's a sample adapted from the documentation on CoroutineScope
, it shows how to use your activity as the coroutine scope:
class MyActivity : AppCompatActivity(), CoroutineScope {
// Sets up the default dispatcher and the root job that we can use to centrally
// cancel all coroutines. We use SupervisorJob to avoid spreading the failure
// of one coroutine to all others.
override val coroutineContext: CoroutineContext =
Dispatchers.Main + SupervisorJob()
override fun onDestroy() {
super.onDestroy()
coroutineContext[Job]!!.cancel()
}
// this.launch picks up coroutineContext for its context:
fun loadDataFromUI() = this.launch {
// Switch to the IO dispatcher to perform blocking IO:
val ioData = withContext(Dispatchers.IO) {
// blocking I/O operations
}
draw(ioData) // use the data from IO to update UI in the main thread
}
}
If you're using a ViewModel
, use it as the scope and cancel the master job from onClear
.
If you're doing work from a background job, use your JobService
implementation as the scope and use onStartJob
and onStopJob
the way we use onCreate
and onDestroy
above.
I was missing the Android portion of coroutines in my gradle file
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.30.0"
Once I had that, Dispatchers.Main appeared
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