Should I pass viewModelScope.coroutineContext to liveData builder function? - android

viewModelScope is used to bind a coroutine lifetime to the ViewModel lifecycle. liveData builder creates a LiveData that runs a coroutine, which lifetime is bound to the LiveData state, so, when the LiveData isn't active, the coroutine cancels after a timeout. Because of the timeout, the coroutine won't be cancelled on configuration change.
If I create a LiveData via liveData builder inside a ViewModel, and observe that LiveData in the Activity, LiveData lifecycle is already bound to the Activity lifecycle. Should I additionally pass the viewModelScope.coroutineContext to the liveData builder? I think that I shouldn't, but in one of the Android documentation samples it passes:
class MyViewModel: ViewModel() {
private val userId: LiveData<String> = MutableLiveData()
val user = userId.switchMap { id ->
liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
emit(database.loadUserById(id))
}
}
}

Q: LiveData lifecycle is already bound to the Activity lifecycle. Should I pass the viewModelScope to the liveData builder?
In simple cases, when an Activity has just one ViewModel, the Activity lifecycle is the same as the ViewModel lifecycle, and it shouldn't change anything, whether you pass 'viewModelScope' to the lifeData builder or not. But in more complex cases, for example, when the ViewModel is shared between fragments, the Fragment lifecycle may not match the shared ViewModel lifecyle, in such cases starting of the coroutine within 'viewModelScope.coroutineContext' makes sense.
So, you can use viewModelScope to provide context to your liveData builder.
Why?
According to official doc: A ViewModelScope is defined for each
ViewModel in your app. Any coroutine launched in this scope is
automatically canceled if the ViewModel is cleared.
So, it helps cancelling current job in your LiveData because, it's now bounded to your ViewModelScope.
Additionally, viewModelScope is helpful if you are computing some data for a layout, you should scope the work to the ViewModel so that if the ViewModel is cleared, the work is canceled automatically to avoid consuming resources.

Related

How to use coroutines in a Kotlin Library

how do i correctly use coroutines in a Library class which has nothing to do with the activity lifecycle?
For now, i created a private property in my class:
private val coroutineScope = CoroutineScope(Dispatchers.Main)
and have a cancel method, to cancel the scope.
public fun cancel() {
coroutineScope.coroutineContext.cancelChildren()
}
Is there any cleaner way to make this work, without having to call cancel on my library class in onPause/OnStop?
EDIT:
Also, an additional question: Does it matter, if my created CoroutineContext is in a Singleton? Like this:
public object MyObject {
private val coroutineScope = CoroutineScope(Dispatchers.Main)
}
Is there any danger of memory leaks or similar?
Simply said no, you create a scope when you need it, you cancel it when you don't need it anymore. Scope takes care of the lifecycle of all the coroutines
fired from it. A coroutine is an instance of suspendable computation. Once you do not need that computation anymore you cancel it, in order to save computational power where it's really needed. To avoid tracking all the fired coroutine by their jobs, we have a scope. Imagine having 1000 independent coroutines and having to track 1000 jobs to cancel them, instead of that, we have a scope to cancel them all at once. You can simply call scope.cancel().
One way how you can avoid manually calling cancel() in onPause/onStop is to use observation pattern, make your library class implement LifecycleObserver interface and have it observe the Lifecycle of an Activity/Fragment of interest.
A Singleton is just a single ever existing instance of a class, there's no reason why would there be any problems having CoroutineScope instance inside of it.
how do i correctly use coroutines in a Library class which has nothing to do with the activity lifecycle?
Well, I would recommend you don't create coroutineScope inside your library class, instead you turn your class function into suspend function by specifying which thread (Dispatcher) it should run on:
suspend fun doWork() {
withContext(Dispatchers.IO) { //run on IO thread
//do your stuff
}
}
AND THEN, use built-in coroutineScope like viewModelScope from ViewModel, or lifecycleScope from Activity / Fragment to execute this suspend function. Those built-in coroutineScopes will be auto-canceld after the ViewModel or Activity / Fragment get destroyed, so you don't need to worry about them:
viewModelScope.launch {
myLibrayObject.doWork() //<- suspend function, you decide the thread inside this function
}
How to choose thred (Dispatcher):
https://youtu.be/ZTDXo0-SKuU?t=392
https://developer.android.com/kotlin/coroutines/coroutines-adv#main-safety

LiveDataScope vs ViewModelScope in Android

I was reading how to use coroutines here https://developer.android.com/topic/libraries/architecture/coroutines. What makes me confused about is the difference between LiveDataScope and ViewModelScope. It sounds like ViewModelScope takes care of lifecycle automatically and you can do network request in the block. When data received from server, post the value to livedata. but then when I continued to read, there's another topic about LiveDataScope which seems redundant to me since you can already accomplish the same result by using ViewModelScope with livedata. What is the main difference between those two? and when should I choose to use one over the other?
Note: This might be late answer for this topic if Author of OP already has understanding about this, But providing some pointers for the referencing comment of #IgorGanapolsky.
Let's see what is the main difference between viewModelScope & LiveDataScope
1. viewModelScope:
Official doc says that, CoroutineScope tied to this ViewModel. This
scope will be canceled when ViewModel will be cleared, i.e
ViewModel.onCleared is called
Meaning that coroutine scope is tied to ViewModel, and once ViewModel gets cleared this scope gets destroyed by cancelling all child coroutine jobs.
Basically, in MVVM pattern we use ViewModel tied to a particular Activity/Fragment. So once that Activity/Fragment gets destroyed, its ViewModel reaches a cleared state. Thus, it cancels all incomplete jobs started by viewModelScope, throwing CancellationException.
So a usecase of viewModelScope is: inside ViewModel when you've got any suspended function to be called and need a CoroutineScope, inspite of making new one you can directly use this one out of the box from viewodel-ktx library.
class SomeViewModel: ViewModel() {
fun someFunction() {
viewModelScope.launch {
callingSomeSuspendedFun()
callingAnotherSuspendedFun()
}
}
}
Note that you don't need to explicitly override onCleared() method of ViewModel to cancel the scope, it does automatically for you, cheers!
2. LiveDataScope:
Now speaking of LiveDataScope, it's actually an interface provided to build better support for LiveData/CoroutineLiveData that can have CoroutineScope out of the box! use livedata-ktx version
Now imagine a situation that you're having a MVVM pattern and wanted to return LiveData from repository to view model. your repository also contains some suspended functions and some coroutine scope.
In that situation when you do some suspended method calls & return the result as live data, there would be some extra work. you'll need transform your data to particular live data after getting it as result. see the example below:
class SomeRepository {
suspended fun someApiCall() : LiveData<Result> {
val result = MutableLiveData<Result>()
someCoroutineScope.launch {
val someData = someOtherCallToGetResult()
result.postValue(someData)
}
return result
}
}
Imagine you had to write above code block due to LiveData didn't had any support for Coroutines ... but until now!
Now you can directly use liveData { } function that returns you LiveData object giving you scope of LiveDataScope in such a way that you can continue your suspended work and emit the result at the same level rather than getting it messy way like above. So above code block can now optimized by following code or better:
class SomeRepository {
suspended fun someApiCall() : LiveData<Result> {
return liveData<Result> {
val someData = someOtherCallToGetResult()
emit(someData)
}
}
}
So use case of liveData would be at repository level when using MVVM pattern if you expose LiveData to viewmodel from respository rather than creating new inside viewmodel. Please note that there's no thumb rule about liveData method shouldn't be used at viewmodel directly. You can if you want to avoid viewModelScope completely.
TL;DR
Check out the liveData method,
Doc states that, The liveData building block serves as a
structured concurrency primitive between coroutines and LiveData. The code block starts executing when LiveData becomes
active and is automatically canceled after a configurable timeout when
the LiveData becomes inactive. If it is canceled before completion,
it is restarted if the LiveData becomes active again. If it
completed successfully in a previous run, it doesn't restart. Note
that it is restarted only if canceled automatically. If the block is
canceled for any other reason (e.g. throwing a
CancelationException), it is not restarted.
I hope that make sense!
The names imply what they actually are:
A ViewModelScope is defined for each ViewModel in your app. Any
coroutine launched in this scope is automatically canceled if the
ViewModel is cleared.
This means that you can do some tasks(like continuous processing) in a coroutine that is in the scope of the ViewModel. The advantage is that you don't have to care anymore when the ViewModel will be stopped to stop your coroutine (this is a big pain when working with global things like java threads). The lifecycle of the ViewModel is related to when an activity is ended.
The LiveDataScope is used for emitting values in the scope of a LiveData object. This means that as long as the LiveData object is alive and there are subscribers that coroutine will work, however once all the subscribers are out the coroutine will stop. This coroutine also restarts once the LiveData is active again.
Basically these are 2 coroutine contexts each responsible for the lifecycle of its element.
PS:
It sounds like ViewModelScope takes care of lifecycle automatically
and you can do network request in the block.
First of all, network requests cannot be done from the Main thread, you usually do them from IO scope, you can read more here. The second thing is that you should take a look at the lifecycle of the ViewModel compared to Activity if you want to understand why LiveDataScope is usually combined with ViewModelScope, you can read about that here.
The short answer to your question is that you cannot be sure that the view is created from the ViewModelScope so if you want to push some updates to UI you should push them as long as someone is subscribed to LiveData, this is where the LiveDataScope comes into play.

Is it OK to launch coroutines from Globalscope on Android in certain situations (singletons)?

When launching coroutines from Activities, Fragments or Android Architecture Components ViewModels, it makes total sense to use a coroutine scope that is bound to the lifecycle of that view component in order to avoid leaks and free resources by e.g. canceling the network request when the user leaves the screen.
But there are other situations, where you don't want to cancel a coroutine even when the user leaves the screen like when you are performing a network request for analytics or writing into a database. Is it OK to launch coroutines with GlobalScope in such situations? The objects where these coroutines are launched are mostly Singletons, so they live for the lifetime of the application anyway, so there is no danger of leaks, right?
The Kotlin docs are pretty clear on GlobalScope:
Application code usually should use an application-defined CoroutineScope. Using async or launch on the instance of GlobalScope is highly discouraged.
Is it OK to use GlobalScope in these situations? If not, how should my application-defined CoroutineScope look like?
If you have an asynchronous worker whose lifecycles is truly global (they only die/end when your process dies), using GlobalScope or a similar life-long scope, is fine.
Say, you have an Activity that makes a request, but the actual network-request needs to continue even if the Activity goes away, because you'd like to cache it when the network finally returns a response.
You'll add a CoroutineScope to your Activity/Fragment, or better to your ViewModel and have your code that finally puts stuff on the screen run in that scope. When the Activity/Fragment/ViewModel dies, the scope is canceled and nothing will be attempted to show something on a screen that no longer exists.
However, your Fragment/Activity/ViewModel may talk to a data-source/repository that has a lifecycle that only ends when the process dies. You can switch to a GlobalScope in there so that your network-responses get cached, even when no Activity/Fragment/ViewModel is alive to show the result on the screen.
class MyViewModel(context: CoroutineContext, repo: MyRepository) : ViewModel() {
private val scope = CoroutineScope(context + SuperviserJob())
override fun onCleared() { scope.cancel() }
fun getDataFromNetwork() {
scope.launch {
myLiveData.value = repo.getDataFromNetwork()
}
}
}
// Singleton class
class MyRepositoryImpl(context: CoroutineContext) : MyRepository {
private val scope = CoroutineScope(context + SupervisorJob())
override suspend fun getDataFromNetwork() : String {
return scope.async { // switch scopes
val data = ... fetch data ...
saveInCache(data)
}.await()
}
}
When your ViewModel ends (onCleared is called), the MyRepositoryImpl's getDataFromNetwork still keeps running and will call saveInCache if all goes right. However, the value returned won't be assigned to myLiveData.value because the coroutine of your ViewModel's scope was cancelled.
Given that you're already trying to attach it to application's lifecycle, I'd suggest either passing the scope to your singleton or implementing a coroutinescope by it. Unfortunately, running coroutines on GlobalScope still might end in leaks.
See this great article by Roman Elizarov for more info:
https://medium.com/#elizarov/the-reason-to-avoid-globalscope-835337445abc

Why Observers added as observeForever to LiveData must be removed?

I've read on Android LiveData documentation that:
You can register an observer without an associated LifecycleOwner object using the observeForever(Observer) method. In this case, the observer is considered to be always active and is therefore always notified about modifications. You can remove these observers calling the removeObserver(Observer) method.
I'm building an app using MVVM architecture pattern using ViewModel and declaring LiveDatas inside my ViewModel class. At my viewModel I have set a observeForever to a LiveData:
val password by lazy {
MutableLiveData<String>()
}
init {
initObservable()
}
private fun initObservable() {
password.observeForever {
...
}
}
From what I understood from the documentation, I should remove the observer everytime the view that instantiates the ViewModel (with the previous code) was destroyed, right? But shouldn't the Observers be destroyed once the view is destroyed (since the ViewModel instance was instantiated in the view and will also be destroyed)?
"I should remove the observer everytime the view that instantiates the ViewModel (with the previous code) was destroyed, right?"
If you are obsering a LiveData in ViewModel using observeForever(observer):
You should not worry about View's lifecycle, because it is different from ViewModel's life. ViewModel should be able to out-live the View that creates it. Instead, the framework will call onCleared() when the ViewModel is not needed, so that's where you should handle removing the observer.
If you are observing a LiveData in View using observe(lifecyclerowner, observer)
Observers will automatically removed by the framework when the lifecycleowner is destroyed.
"But shouldn't the Observers be destroyed once the view is destroyed (since the ViewModel instance was instantiated in the view and will also be destroyed)?"
This question is more of a Java question than Android.
Think about what it means by "being destroyed". When a View or ViewModel is destroyed by the Android Framework, it does not mean that the object is completely removed from the memory. Your activities and fragments will not be garbage collected as long as there are other objects (such as observer) that has reference to them.
If you call observe(activity, observer), then the Android Framework can track the connection between the activity instance and the observer instance, and therefore it can kill observer when it wants to kill activity. However if you simply call observeForever(observer) there is simply no way for Android Framework to tell which object this observer belongs to.
Implementing Sanlok Lee's answer in a ViewModel, it would look like this:
val password by lazy {
MutableLiveData<String>()
}
private val passwordObserver = Observer<String> {
...
}
init {
initObservable()
}
private fun initObservable() {
password.observeForever(passwordObserver)
}
override fun onCleared() {
password.removeObserver(passwordObserver)
super.onCleared()
}
From what I understood from the documentation, I should remove the
observer everytime the view that instantiates the ViewModel
To achieve this you should instantiate your viewmodel inside the View (Activity, Fragment) and observe the livedata like this
val model = ViewModelProviders.of(this).get(MyViewModel::class.java)
model.getUsers().observe(this, Observer<List<User>>{ users ->
// update UI
})
by passing this you tie observing livedata to view's lifecycle, so when View (Activity, Fragment) will be destroyed both viewmodel and observer will be destroyed.

Cancel all pending Kotlin jobs from ViewModel's onCleared

With Kotlin 1.3 one can launch job using GlobalScope.launch but one thing that I can't seem to figure out is how to keep track of Job returned by ``GlobalScope.launch` and cancel all pending jobs if they are active.
In older version of launch one could specify parent = parentJob and one could simply cancel parentJob. But when using GlobalScope.launch how does one cancel all pending jobs (easily) so from say ViewModel's onCleared one can cancel all pending stuff.
So basically it turns out you can either have your ViewModel/AppComptActivity etc. inherit from CoroutineScope. Or you can use composition like this:
```
private val pendingJobs = Job()
private val coroutineScope = CoroutineScope(contextProvider.io + pendingJobs)
...
...
coroutineScope.launch {
withContext(contextProvider.UI) {
}
}
```
Then in appropriate destroy method call pendingJobs.cancel() to terminate pending jobs.
Using GlobalScope is highly discouraged. In the Android KTX libraries there are handy scopes that can be used to launch coroutines. Using them you don't need to think about cancelling jobs, they designed to do it by themselves. CoroutineScopes:
In ViewModel class it is viewModelScope. The CoroutineScope is bound to Dispatchers.Main and is automatically cancelled when the ViewModel is cleared. You can use viewModelScope instead of creating a new scope for each ViewModel.
Dependency:
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"
In Activity/Fragment there is lifecycleScope. Any coroutine launched in this scope is canceled when the Lifecycle is destroyed. You can access the CoroutineScope of the Lifecycle by using the lifecycle.coroutineScope or lifecycleOwner.lifecycleScope properties.
Dependency:
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0"
Example of launching a coroutine in ViewModel:
viewModelScope.launch {...}

Categories

Resources