How to create Life Cycle Aware Handler in Android? - android

First of all, I know how to create Handler.
I am working on a project where I am using Handler with postDelayed. There are some times when app got crashed because activity was destroyed and the task inside the handler executes after activity destroy.
I am looking for an alternative of Handler which can execute after a delay and it could be Lifecycle Aware so that the app won't get crash.
I know how to cancel Handler (removing Handler or cancelling handler inside onDestroy/onStop methods of activity), here is the link for the same. But I am not looking for these solutions. Any alternative would be a better solution if one can have.
Thanks in advance!

Depending on if you're using java or Kotlin, you can use RxJava or coroutines for this.
RxJava solution
// this should be a member variable
private final CompositeDisposable disposables = new CompositeDisposable();
// this is how you launch the task that needs delay
Disposable d = Single.timer(2, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.observeOn(schedulers.ui())
.subscribe(ignored -> {
// you can manipulate the ui here
});
// make sure to call disposables.clear() in onDestroyView
disposables.add(d);
Kotlin solution
viewLifecycleOwner.lifecycleScope.launchWhenResumed {
withContext(Dispatchers.IO) {
delay(2000)
}
// you can manipulate the ui here
}
As you can see the Kotlin + coroutines solution requires much less manual work, and it's harder to get wrong, so if you're on a Kotlin project I think you should use that one. Other alternative may be to use Guava ListenableFutures but I haven't work with those yet.

If you are using a Handler to execute delayed actions with postDelayed() you can run into troubles when the execution of the action happens after your Activity or Fragment has been destroyed.
There is a simple solution to this. Bind your Handler to the lifecycle.
Create a LifecycleObserver
First lets create a LifecycleObserver that gets a Handler instance.
In the event of Lifecycle.Event.ON_DESTROY it will remove all callbacks and messages from that Handler.
class LifecycleObververHandler(private val handler: Handler) : LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
internal fun onDestroy() {
handler.removeCallbacksAndMessages(null)
}
}
Add the LifecycleObserver to the LifecycleOwner
Next we have to add the LifecycleObververHandler to a LifecycleOwner. We also wanna create these lifecycle observed handlers easily. So lets create a LifecycleHandlerFactory.
That factory gets created with a lambda handlerFactory that gives you an instance of a Handler (default is a Handler with a main Looper). It has one function create that expects a LifecycleOwner.
Within that function it checks that the state of the Lifecycle is not DESTROYED. It calls the handlerFactory to get an instance of Handler. Then it creates a LifecycleObserverHandler, which takes the handler, and adds that Observer to the LifecycleOwner. Finally the Handler gets returned.
class LifecycleHandlerFactory(private val handlerFactory: (() -> Handler) = { Handler(Looper.getMainLooper()) }) {
fun create(owner: LifecycleOwner): Handler {
check(owner.lifecycle.currentState != Lifecycle.State.DESTROYED) {
"Cannot create a Handler for a destroyed life-cycle"
}
val handler = handlerFactory.invoke()
val observer = LifecycleObververHandler(handler)
owner.lifecycle.addObserver(observer)
return handler
}
}
Inject the lifecycle aware Handler
When you are using a DependendencyInjection Framework or a service locater like Koin you can inject the lifecycle aware Handler.
module {
// a single instance of LifecycleHandlerFactory
// it gets a lambda that every time its being called returnes a new Handler with a main looper.
single { LifecycleHandlerFactory() }
// uses the LifecycleHandlerFactory to create a new handler with a LifecycleOwner as parameter.
factory<Handler> { (lifecycleOwner: LifecycleOwner) -> get<LifecycleHandlerFactory>().create(lifecycleOwner) }
}
Finally you can inject a lifecycle handler in your Fragment (or Activity).
// injects a new handler with a LifecycleOwner as a parameter
private val handler: Handler by inject { parametersOf(viewLifecycleOwner) }

if you are familiar and ok with using coroutines, you can replace Handlers to achieve the same functionality
using below dependency with coroutines you can make coroutines lifecycle aware
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
then in Activity
lifeCycleScope.launchWhenStarted{
delay(1000)
//do your work after delay, runs on main thread
//by default, will be cancelled if life cycle is inactive
}
More about using coroutines : Deep Dive into Coroutines + Android

Related

Timer implementation in Android on Kotlin: Should one use coroutine with delay or CountDownTimer

As per the commentary of this answer, using CountDownTimer
val timer=object:CountDownTimer(Long.MAX_VALUE,10){
override fun onTick(p0: Long) {
_mutableLiveData.postValue(newValue)
}
override fun onFinish() {
TODO("Not yet implemented")
}
}.also { it.start() }
from inside a ViewModel or otherwise would cause memory leaks. On the other hand implementing a timer using viewModelScope.launch
viewModelScope.launch {
while (true){
_mutableLiveData.postValue(newValue)
delay(10)
}
}
from inside the same ViewModel wastes resources as a thread should exit after performing its task instead of going to sleep.
Which way should I use?
Is there some other idiomatic way that I am missing out?
The context of my question is this: in a ViewModel, my timer implementation (currently using delay) periodically changes the state of a private MutableLiveData that is being observedAsState in a #Composable
A CountDownTimer only leaks memory if you don't clean it up when its associated objects are passing out of scope. So in a ViewModel, you should store a reference to it in a property and call cancel() on it in onCleared(). In an Activity, you'd cancel it in onDestroy(). And so on.
The viewModelScope is set up to automatically cancel any coroutines it's running when the ViewModel is destroyed, so you don't have to worry about leaks.
Likewise, lifecycleScope in an Activity or Fragment do the same, and viewLifecycle.lifecycleScope does it for the View life of a Fragment.
Calling sleep in a thread prevents that thread from doing any work, so it's hogging a core of the processor. And it must never be done on the Main thread because that would lock the UI.
In a coroutine, delay does not lock up any threads or coroutines, so the same concerns do not apply.

Android CoroutineScope Auto Cancel after It Finishes

I want to know whether coroutineScope will be auto-canceled after its work is finished. Say I create a coroutineScope in a custom class Rather Than ViewModel class or Fragment / Activity class:
class MyClass {
private val backgroundScope = CoroutineScope(Dispatchers.Default)
fun doSomething() = backgroundScope.launch {
//do background work
}
}
In this case, after the background work is done, would backgroundScope auto-cancel itself?
A CoroutineScope consists of a CoroutineContext. A CoroutineContext consist of 2 primary elements,a Job and a ContinuationInterceptor(usually just a CoroutineDispatcher), other elements are CoroutineExceptionHandler and CoroutineName.
If a coroutine finishes it won't cancel the scope (that is its Job).
Even if you cancel the coroutine's job it won't cancel the scope (that is its Job). Because each time you fire a coroutine using a specific scope, the coroutine's job becomes a child job of the scope's job.
In your case backgroundScope, you did not specify a Job yourself, if you dig into the source code, you'll see that in the absence of a Job, a Job instance is provided..
val coroutineJob = backgroundScope.launch { ... }
When this coroutineJob finishes or is cancelled, it will not cancel the Job inside the backgroundScope, therefore the backgroundScope won't be cancelled.
It won't auto-cancel. It stays open for any number of jobs to be launched on it. However, you may want to give the scope a SupervisorJob() as its root context if you don't want it to be killed by any child jobs completing exceptionally. If that happened, subsequent coroutines on the scope wouldn't start.
private val backgroundScope = CoroutineScope(SupervisorJob())
The reason lifecycleScope and viewModelScope are automatically cancelled when their associated class is destroyed is because under the hood, Jetpack subscribes to the lifecycle of the associated class and manually cancels the scope in onDestroy().

GlobalScope vs CoroutineScope vs lifecycleScope

I am used to working with AsyncTask and understand it pretty well due to its simplicity. But Coroutines are confusing to me. Can you please explain to me in a simple way what is the difference and purpose of each of the following?
GlobalScope.launch(Dispatchers.IO) {}
GlobalScope.launch{}
CoroutineScope(Dispatchers.IO).launch{}
lifecycleScope.launch(Dispatchers.IO){}
lifecycleScope.launch{}
First, let's start with definitions to make it clear. If you need a tutorial or playground for Coroutines and Coroutines Flow you can check out this tutorial/playground i created.
Scope is object you use to launch coroutines that only contains one object which is CoroutineContext
public interface CoroutineScope {
/**
* The context of this scope.
* Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
* Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.
*
* By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
*/
public val coroutineContext: CoroutineContext
}
The coroutine context is a set of rules and configurations that define
how the coroutine will be executed.
Under the hood, it’s a kind of map, with a set of possible keys and values.
Coroutine context is immutable, but you can add elements to a context using plus operator,
just like you add elements to a set, producing a new context instance
The set of elements that define the behavior of a coroutine are:
CoroutineDispatcher — dispatches work to the appropriate thread.
Job — controls the lifecycle of the coroutine.
CoroutineName — name of the coroutine, useful for debugging.
CoroutineExceptionHandler — handles uncaught exceptions
Dispatchers
Dispatchers determine which thread pool should be used. Dispatchers class is also
CoroutineContext which can be added to CoroutineContext
Dispatchers.Default: CPU-intensive work, such as sorting large lists, doing complex calculations and similar. A shared pool of threads on the JVM backs it.
Dispatchers.IO: networking or reading and writing from files.
In short – any input and output, as the name states
Dispatchers.Main: mandatory dispatcher for performing UI-related events in Android's main or UI thread.
For example, showing lists in a RecyclerView, updating Views and so on.
You can check out Android's official documents for more info on dispatchers.
Edit Even though official document states that
Dispatchers.IO - This dispatcher is optimized to perform disk or
network I/O outside of the main thread. Examples include using the
Room component, reading from or writing to files, and running any
network operations.
Answer from Marko Topolnic
IO runs the coroutine on a special, flexible thread pool. It exists
only as a workaround when you are forced to use a legacy, blocking IO
API that would block its calling thread.
might be right either.
Job A coroutine itself is represented by a Job.
A Job is a handle to a coroutine. For every coroutine that you create (by launch or async),
it returns a Job instance that uniquely identifies the coroutine and manages its lifecycle.
You can also pass a Job to a CoroutineScope to keep a handle on its lifecycle.
It is responsible for coroutine’s lifecycle, cancellation, and parent-child relations.
A current job can be retrieved from a current coroutine’s context:
A Job can go through a set of states: New, Active, Completing, Completed, Cancelling and Cancelled.
while we don’t have access to the states themselves,
we can access properties of a Job: isActive, isCancelled and isCompleted.
CoroutineScope It is defined a simple factory function that takes CoroutineContexts as arguments to create wrapper around the combined CoroutineContext as
public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
ContextScope(if (context[Job] != null) context else context + Job())
internal class ContextScope(context: CoroutineContext) : CoroutineScope {
override val coroutineContext: CoroutineContext = context
// CoroutineScope is used intentionally for user-friendly representation
override fun toString(): String = "CoroutineScope(coroutineContext=$coroutineContext)"
}
and creates a Job element if the provide context does not have one already.
Let's look at GlobalScope source code
/**
* 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.
* Another use of the global scope is operators running in [Dispatchers.Unconfined], which don't have any job associated with them.
*
* Application code usually should use an application-defined [CoroutineScope]. Using
* [async][CoroutineScope.async] or [launch][CoroutineScope.launch]
* on the instance of [GlobalScope] is highly discouraged.
*
* Usage of this interface may look like this:
*
* ```
* fun ReceiveChannel<Int>.sqrt(): ReceiveChannel<Double> = GlobalScope.produce(Dispatchers.Unconfined) {
* for (number in this) {
* send(Math.sqrt(number))
* }
* }
* ```
*/
public object GlobalScope : CoroutineScope {
/**
* Returns [EmptyCoroutineContext].
*/
override val coroutineContext: CoroutineContext
get() = EmptyCoroutineContext
}
As you can see it extends CoroutineScope
1- GlobalScope.launch(Dispatchers.IO) {} GlobalScope is alive as long as you app is alive, if you doing some counting for instance in this scope and rotate your device it will continue the task/process.
GlobalScope.launch(Dispatchers.IO) {}
runs as long as your app is alive but in IO thread because of using Dispatchers.IO
2- GlobalScope.launch{} It's same as the first one but by default, if you don't have any context, launch uses EmptyCoroutineContext which uses Dispatchers.Default, so only difference is thread with first one.
3- CoroutineScope(Dispatchers.IO).launch{} This one is the same as first one with only syntax difference.
4- lifecycleScope.launch(Dispatchers.IO){} lifecycleScope is an extention for LifeCycleOwner and bound to Actvity or Fragment's lifCycle where scope is canceled when that Activity or Fragment is destroyed.
/**
* [CoroutineScope] tied to this [LifecycleOwner]'s [Lifecycle].
*
* This scope will be cancelled when the [Lifecycle] is destroyed.
*
* This scope is bound to
* [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate].
*/
val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
get() = lifecycle.coroutineScope
You can also use this as
class Activity3CoroutineLifecycle : AppCompatActivity(), CoroutineScope {
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main + CoroutineName("🙄 Activity Scope") + CoroutineExceptionHandler { coroutineContext, throwable ->
println("🤬 Exception $throwable in context:$coroutineContext")
}
private val dataBinding by lazy {
Activity3CoroutineLifecycleBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(dataBinding.root)
job = Job()
dataBinding. button.setOnClickListener {
// This scope lives as long as Application is alive
GlobalScope.launch {
for (i in 0..300) {
println("🤪 Global Progress: $i in thread: ${Thread.currentThread().name}, scope: $this")
delay(300)
}
}
// This scope is canceled whenever this Activity's onDestroy method is called
launch {
for (i in 0..300) {
println("😍 Activity Scope Progress: $i in thread: ${Thread.currentThread().name}, scope: $this")
withContext(Dispatchers.Main) {
dataBinding.tvResult.text = "😍 Activity Scope Progress: $i in thread: ${Thread.currentThread().name}, scope: $this"
}
delay(300)
}
}
}
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
}
TL;DR
GlobalScope.launch(Dispatchers.IO): Launches a top-level coroutine on Dispatchers.IO. Coroutine is unbound and keeps running until finished or cancelled. Often discouraged since programmer has to maintain a reference to join() or cancel().
GlobalScope.launch: Same as above, but GlobalScope uses Dispatchers.Default if not specified. Often discouraged.
CoroutineScope(Dispatchers.IO).launch: Creates a coroutine scope which uses Dispatchers.IO unless a dispatcher is specified in the coroutine builder i.e. launch
CoroutineScope(Dispatchers.IO).launch(Dispatchers.Main): Bonus one. Uses the same coroutine scope as above (if the scope instance is same!) but overrides Dispatcher.IO with Dispatchers.Main for this coroutine.
lifecycleScope.launch(Dispatchers.IO): Launches a coroutine within the lifecycleScope provided by AndroidX. Coroutine gets cancelled as soon as lifecycle is invalidated (i.e. user navigates away from a fragment). Uses Dispatchers.IO as thread pool.
lifecycleScope.launch: Same as above, but uses Dispatchers.Main if not specified.
Explanation
Coroutine scope promotes structured concurrency, whereby you can launch multiple coroutines in the same scope and cancel the scope (which in turn cancels all the coroutines within that scope) if the need be. On the contrary, a GlobalScope coroutine is akin to a thread, where you need to keep a reference in-order to join() or cancel() it. Here's an excellent article by Roman Elizarov on Medium.
CoroutineDispatcher tells the coroutine builder (in our case launch {}) as to which pool of threads is to be used. There are a few predefined Dispatchers available.
Dispatchers.Default - Uses a thread pool equivalent to number of CPU cores. Should be used for CPU bound workload.
Dispatchers.IO - Uses a pool of 64 threads. Ideal for IO bound workload, where the thread is usually waiting; maybe for network request or disk read/write.
Dispatchers.Main (Android only): Uses main thread to execute the coroutines. Ideal for updating UI elements.
Example
I've written a small demo fragment with 6 functions corresponding to the above 6 scenarios. If you run the below fragment on an Android device; open the fragment and then leave the fragment; you'll notice that only the GlobalScope coroutines are still alive. Lifecycle coroutines are cancelled by lifecycleScope when the lifecycle is invalid. On the other hand, CoroutineScope ones are cancelled on onPause() invocation which is explicitly done by us.
class DemoFragment : Fragment() {
private val coroutineScope = CoroutineScope(Dispatchers.IO)
init {
printGlobalScopeWithIO()
printGlobalScope()
printCoroutineScope()
printCoroutineScopeWithMain()
printLifecycleScope()
printLifecycleScopeWithIO()
}
override fun onPause() {
super.onPause()
coroutineScope.cancel()
}
private fun printGlobalScopeWithIO() = GlobalScope.launch(Dispatchers.IO) {
while (isActive) {
delay(1000)
Log.d("CoroutineDemo", "[GlobalScope-IO] I'm alive on thread ${Thread.currentThread().name}!")
}
}
private fun printGlobalScope() = GlobalScope.launch {
while (isActive) {
delay(1000)
Log.d("CoroutineDemo", "[GlobalScope] I'm alive on ${Thread.currentThread().name}!")
}
}
private fun printCoroutineScope() = coroutineScope.launch {
while (isActive) {
delay(1000)
Log.d("CoroutineDemo", "[CoroutineScope] I'm alive on ${Thread.currentThread().name}!")
}
Log.d("CoroutineDemo", "[CoroutineScope] I'm exiting!")
}
private fun printCoroutineScopeWithMain() = coroutineScope.launch(Dispatchers.Main) {
while (isActive) {
delay(1000)
Log.d("CoroutineDemo", "[CoroutineScope-Main] I'm alive on ${Thread.currentThread().name}!")
}
Log.d("CoroutineDemo", "[CoroutineScope-Main] I'm exiting!")
}
private fun printLifecycleScopeWithIO() = lifecycleScope.launch(Dispatchers.IO) {
while (isActive) {
delay(1000)
Log.d("CoroutineDemo", "[LifecycleScope-IO] I'm alive on ${Thread.currentThread().name}!")
}
Log.d("CoroutineDemo", "[LifecycleScope-IO] I'm exiting!")
}
private fun printLifecycleScope() = lifecycleScope.launch {
while (isActive) {
delay(1000)
Log.d("CoroutineDemo", "[LifecycleScope] I'm alive on ${Thread.currentThread().name}!")
}
Log.d("CoroutineDemo", "[LifecycleScope] I'm exiting!")
}
}
I'd organize your list along three axes:
GlobalScope vs. CoroutineScope() vs. lifecycleScope
Dispatchers.IO vs. inherited (implicit) dispatcher
Specify the dispatcher in the scope vs. as an argument to launch
1. Choice of Scope
A big part of Kotlin's take on coroutines is structured concurrency, which means all the coroutines are organized into a hierarchy that follows their dependencies.
If you're launching some background work, we assume you expect its results to appear at some point while the current "unit of work" is still active, i.e., the user hasn't navigated away from it and doesn't care anymore about its result.
On Android, you have the lifecycleScope at your disposal that automatically follows the user's navigation across UI activities, so you should use it as the parent of background work whose results will become visible to the user.
You may also have some fire-and-forget work, that you just need to finish eventually but the user doesn't await its result. For this you should use Android's WorkManager or similar features that can safely go on even if the user switches to another application. These are usually tasks that synchronize your local state with the state kept on the server side.
In this picture, GlobalScope is basically an escape hatch from structured concurrency. It allows you to satisfy the form of supplying a scope, but defeats all the mechanisms it's supposed to implement. GlobalScope can never be cancelled and it has no parent.
Writing CoroutineScope(...).launch is just wrong because you create a scope object without a parent that you immediately forget, and thus have no way of cancelling it. It's similar to using GlobalScope but even more hacky.
2. Choice of Dispatcher
The coroutine dispatcher decides which threads your coroutine may run on. On Android, there are three dispatchers you should care about:
Main runs everything on the single GUI thread. It should be your main choice.
IO runs the coroutine on a special, flexible thread pool. It exists only as a workaround when you are forced to use a legacy, blocking IO API that would block its calling thread.
Default also uses a thread pool, but of fixed size, equal to the number of CPU cores. Use it for computation-intensive work that would take long enough to cause a glitch in the GUI (for example, image compression/decompression).
3. 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. lifecycleScope specifies the Main dispatcher.
We already explained that you shouldn't create ad-hoc scopes using the CoroutineScope constructor, so the proper place to specify an explicit dispatcher is as a parameter to launch.
In technical detail, when you write someScope.launch(someDispatcher), the someDispatcher argument is actually a full-fledged coroutine context object which happens to have a single element, the dispatcher. The coroutine you're launching creates a new context for itself by combining the one in the coroutine scope and the one you supply as a parameter. On top of that, it creates a fresh Job for itself and adds it to the context. The job is a child of the one inherited in the context.
You should know that if you want to launch suspend function you need to do it in CoroutineScope. Every CoroutineScope have CoroutineContext. Where CoroutineContext is a map that can contain Dispatcher (dispatches work to the appropriate thread), Job
(controls the lifecycle of the coroutine), CoroutineExceptionHandler (handles uncaught exceptions), CoroutineName (name of the coroutine, useful for debugging).
GlobalScope.launch(Dispatchers.IO) {} - GlobalScope.launch creates global coroutines and using for operations that should not be canceled, but a better alternative would be creating a custom scope in the Application class, and inject it to the class that needs it. This has the advantage of giving you the ability to use CoroutineExceptionHandler or replace the CoroutineDispatcher for testing.
GlobalScope.launch{} - same as GlobalScope.launch(Dispatchers.IO) {} but runs coroutines on Dispatchers.Default.Dispatchers.Default is a default Dispatcher that is used if no dispatchers is specified in their context.
CoroutineScope(Dispatchers.IO).launch{} - it's create scope with one parameter and launch new coroutine in it on IO thread. Will be destroyed with object where it was launched. But you should manually call .cancel() for CoroutineScope if you want to end your work properly.
lifecycleScope.launch(Dispatchers.IO){} - it is existing scopes that available from a Lifecycle or from a LifecycleOwner (Activity or Fragment) and comes in your project with dependency androidx.lifecycle:lifecycle-runtime-ktx:*. Using it you can rid from manualy creating CoroutineScope. It will run your job in Dispatchers.IO without blocking MainThread, and be sure that your jobs will be cancelled when your lifecycle is destroyed.
lifecycleScope.launch{} - same as lifecycleScope.launch(Dispatchers.IO){} that create CoroutinesScope for you with default Dispatchers.Main parameter and runs your coroutines in Dispatcher.Main that mean you can work with UI.

Android Livedata Observer Coroutine Kotlin

Is it possible to have a co-routine inside an observer to update the UI?
For Example:
Viewmodel.data.observer(this, Observer{ coroutinescope })
You can run any code you want from the Observer callback. But it would not be a good idea to launch a coroutine that updates the UI later, because when the coroutine is complete, the UI might be destroyed, which can cause exceptions to be thrown and crash your app.
Just run the UI update code directly from the Observercallback.
viewModel.data.observe(this, Observer {
// Update the UI here directly
})
That way you know that UI is alive when you update it, since LiveData takes the lifecycle of this into account.
If you want to launch some coroutine at the same time as the callback, it would be better to do it within your viewModel using viewModelScope.
// This triggers the above code
data.value = "foo"
// Now also launch a network request with a coroutine
viewModelScope.launch {
val moreData = api.doNetworkRequest()
// Set the result in another LiveData
otherLiveData.value = moreData
}
Note that you must add a dependency to build.gradle in order to use viewModelScope:
dependencies {
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0'
}
Yes, it's possible. You can launch a GlobalScope coroutine and when you need update the UI you should be that in activity!!.runOnUiThread
Here a sample.
viewModel.phoneNumber.observe(this, Observer { phoneNumber ->
GlobalScope.launch {
delay(1000L) //Wait a moment (1 Second)
activity!!.runOnUiThread {
binding.textviewPhoneLabel.edittextName.setText(phoneNumber)
}
}
})

How to use coroutines GlobalScope on the main thread?

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?
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

Categories

Resources