Android Livedata Observer Coroutine Kotlin - android

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)
}
}
})

Related

Cancelling custom CoroutineScope

val scope = CoroutineScope(
Job() + Dispatchers.Main
)
scope.launch {
beforeExecute()
val result = withContext(dispatcher) { doInBackground(*params) }
if (!isCancelled) {
postExecute(result)
} else {
cancelled(result)
}
status = Status.FINISHED
}
scope.cancel()
If i put scope.cancel() outside launch it cancels the coroutine immediately without calling launch block code.Is this expected?Why it happens?Should cancel be placed inside launch at end of launch block if i want coroutine to end once it finish executing code inside launch?
Update
As per Hau Luu's answer and Marko Topolnik's comment,
”at the end of launch, I think the task is done and you don't need to
manually cancel the Coroutine.”
and
“Once your task is done, the coroutine disappears from memory.”
But here in Case 2 ,if I start another launch it is executed unless we cancel the coroutine inside first launch as in Case 1.
So is there any surety that after task is completed the coroutine disappears from memory without us manually calling cancel() ?Bcoz compiler will never know which is the last launch that is going to execute after which it needs to end coroutine
Case 1
scope.launch {
Log.e("Task","1");
scope.cancel()
}
scope.launch {
Log.e("Task","2");
}
Only Task 1 is printed
Case 2
scope.launch {
Log.e("Task","1");
}
scope.launch {
Log.e("Task","2");
}
Both Task 1 and 2 are printed
Your code can be translated to natural language as "Cancel the given coroutine right after scope.launch is executed" so I think this is expected behavior.
And for the other question, we only want to cancel a coroutine when there is something wrong during the execution process - hey coroutine, during the execution of the task I gave you. if there is sth wrong happen. Kill yourself. So at the end of launch, I think the task is done and you don't need to manually cancel the Coroutine.
Update: I write this as an answer because I can't write code in comment.
CoroutineScope was designed to react to the lifecycle of the object that create/start/house a coroutine. So when you call the cancel method on a CoroutineScope, you're stoping everything. Stoping not canceling. All child coroutines that were created by the scope, all jobs they are executing, cancel them all, and nothing more. The job is done. That's is why you can't start another launch after scope.cancel
A CoroutineScope will create and hold refs to a bunch of Corrountine via builder methods like launch and async. When you want to cancel a specific Coroutine. You need to cancel the Job that returned by the builder. Not cancel the scope that is housing them.
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
val job1 = scope.launch{ print('Task 1') }
job1.cancel()
val job2 = scope.launch{ print('Task 2') }
Task 2 will be printed as normal.

How to create Life Cycle Aware Handler in 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

Using Firebase with Kotlin coroutines: Task is not canceled when job is canceled

Inside a coroutine, with the help of await() function from "kotlinx-coroutines-play-services" library, i use something like this:
suspend fun uploadFile(uri: Uri) = withContext(IO) {
Firebase.storage.reference.child("example").putFile(uri).await()
}
The problem is when the Job of the current coroutine is canceled, this task is not canceled with it and keeps executing.
I need all nested tasks to auto-stop when the job is canceled. Is there a way I can achieve this?
If I understand you correctly you have your repository that is is application scoped, so that there is one instance of it in your app.
Let's assume you call your repository function from the ViewModel. You can use viewModelScope to call it from a coroutine that will be lifecycle aware and it will be stopped when the viewModel is destroyed.
It could look like this:
fun uploadFile(uri: Uri) = viewModelScope.launch(Dispatchers.IO) {
repo.uploadFile(uri)
}
And the repository function could now look like this:
suspend fun uploadFile(uri: Uri) {
Firebase.storage.reference.child("example").putFile(uri).await()
}
If you call it from the activity or the fragment not viewModel you can instead write:
lifecycleScope.launch(Dispatchers.IO){
repo.uploadFile(uri)
}
If you have nested calls like the repository is called by like a UseCase or sth else, you just need to add suspend keyword in every function on the way.
Edit:
You can cancel the coroutine, but unfortunately you cannot cancel the firebase request. So you want to handle the situation when you cancel the coroutine and the file should not be saved remotely. One simple way is to handle it in onDetach or sth else in fragment or activity. One trick you could use is to put you code in the repository in try block and add finally block. It will be run when the coroutine is canceled and there you could check whether file is saved and if so, delete it.
suspend fun uploadFile(uri: Uri) {
try {
Firebase.storage.reference.child("example").putFile(uri).await()
} finally {
// here handle canceled coroutine
}
}
You can read more about it here.

withContext after a CoroutineScope.launch - will CoroutineScope.launch block?

I just stumbled over this code:
fun addHeaderAndSubmitList(list: List<SleepNight>?) {
adapterScope.launch {
val items = when (list) {
null -> listOf(DataItem.Header)
else -> listOf(DataItem.Header) + list.map { DataItem.SleepNightItem(it) }
}
// isn't there any code required to wait for the
// adapterScope.launch coroutine to finish?
withContext(Dispatchers.Main) {
submitList(items)
}
}
}
found in this file of the google sleeptracker example.
I already added my question as comment in the code example. I am new to coroutines but to my knowledge adapterScope.launch is non-blocking, so adapterScope.launch might not be finished until
withContext(Dispatchers.Main) {
submitList(items)
}
is reached? Am I wrong about this? If not, how to fix it?
See launch.
Launches a new coroutine without blocking the current thread [...]
Here's what happens:
addHeaderAndSubmitList uses launch to start some asynchronous work. The work will finish naturally or will be terminated when adapterScope's lifecycle ends. Meanwhile addHeaderAndSubmitList finishes immediately.
Whatever is inside launch {} runs sequentially. submitList(items) is called after val items = .... Each happens effectively on a different thread, but the order is guaranteed.
The code inside launch { } runs sequentially in a blocking fashion inside the adapterScope, meaning all the code above the withContext(Main) is run and finished before switching contexts to submit the list to the adapter on the main thread.
The entire code block is likely running in an Default or IO context, so it runs in a blocking fashion outside the main thread, until it reaches the withContext(Main) to post the results to the main thread.

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