Why Observers added as observeForever to LiveData must be removed? - android

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.

Related

Observe live data objects in fragment using activity context?

I'm using navigation bottom with shared ViewModel with all fragments inside navigation bottom but it throws this exception when recall fragment second time
java.lang.IllegalArgumentException: Cannot add the same observer with different lifecycles
I have tried to make all observers attached to activity not to it's fragment as below
1-Declare viewModel in fragemt
viewModel = activity?.run {
ViewModelProviders.of(this,viewModelFactory).get(SharedViewModel::class.java)
} ?: throw Exception("Invalid Activity")
2-Observer livedata object
viewModel.msg.observe(activity!!, Observer {
Log.i(TAG,it)
})
3- remove observer
override fun onStop() {
super.onStop()
viewModel.msg.removeObservers(activity!!)
}
This code is working fine with me, but I wondering if my code is correct and working probably?
thanks in advance
It is a common mistake we do while using live-data in fragment. Using this/activity on fragment can be duplicate. You should use viewLifecycleOwner for livedata observing in fragment.
viewModel.msg.observe(viewLifecycleOwner, Observer {
Log.i(TAG,it)
})
For more information, read this article https://medium.com/#cs.ibrahimyilmaz/viewlifecycleowner-vs-this-a8259800367b
You don't need to remove the observer manually.
Why you are adding the observer to the fragment with the activity lifecycle? If you have some logic that needs to be executed when fragment is not active, add it to your activity. So instead of what you have, you need:
viewModel.msg.observe(this, Observer {
Log.i(TAG, it)
})
What happens in your case is that each time you reopen your fragment, you attach a new observer with the same lifecycle, which seems to be an error. Livedata observers were specifically designed so that you don't have to write code for handling lifecycles manually.

Should I pass viewModelScope.coroutineContext to liveData builder function?

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.

How can I get SAM Interface's object in Kotlin

Suppose I am Observing a LiveData in a Fragment and I want to remove observer after I received the data.
eg:
val testLiveData = MutableLiveData<String>()
and Observing as:
testLiveData.observe(this, Observer<String> {
//TODO://Remove this Observer from here
//testLiveData.removeObserver(this)
})
How can I do that? Calling "this" is giving me an instance of the Fragment instead of the current Observer.
However, I can do like this.
testLiveData.observe(this, object : Observer<String>{
override fun onChanged(t: String?) {
testLiveData.removeObserver(this)
}
})
Is there any way to do the same in SAM?
In the first case you cannot access this since it is not guaranteed that every invocation of observe creates a new instance of Observer<String>.
If the lambda does not access any variable within the function where it is defined, the corresponding anonymous class instance is reused between calls (i.e., a singleton Observer is created that is used for every observe call).
Thus, for implementing listeners, the second variant (object : Observer<String>) should be used. This enforces that a new Observer is created every time observe is called, which in turn can then be accessed as this within its implemented methods.

How to connect View and ViewModel with respect to screen rotation?

One promise of the ViewModel is, that it survives cases like rotation of the screen. I still try to figure out how to organise this in practice.
On certain events of the model the View should update. There are two major options:
The ViewModel updates the View.
The View observes the ViewModel and updates itself.
In the first case the ViewModel needs a link to the View. I could inject the View into the ViewModel, yet my feeling is it would be better to inject the VieModel into the View.
What is the better style to join them?
Then after rotation the onCreate() method is called again triggering initialisations of the ViewModel a second time. I need to check for this else I am in danger to register listeners to the actual model twice and thrice and similar issues. I may even need to clean up relations to the old view first.
This checking feels kind of unclean. I would expect a dedicated API for this in the ViewModel, if this would be a standard practice. Without I have the feeling to be on the wrong track.
What are good patterns to deal with this in a clean standard way?
Soo.. You don't really have to connect the ViewModel and the Activity/Fragment "with respect to screen rotation", you get that for free - that's one of the perks.
The official documentation is really good.
You connect a ViewModel to your view in onCreate() by something like
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.getUsers().observe(this, users -> {
updateUI()
});
}
}
And while it is true as you say that orientation change will trigger onCreate() again, it's not true that this will create a new ViewModel. The MyViewModel is only created the first time around in onCreate. Re-created activities receive the same MyViewModel instance created by the first activity. This is even true for different fragments/activities referencing the same ViewModel.
You should never ever inject a view into the ViewModel. It's the equivalent of drowning puppies. If you need a context in the ViewModel, extend AndroidViewModel instead (and pass it the Application).
What you do is that you create a ViewModel that holds all state. And handles fetching data from network or disk or what not. All that is not UI related goes in to the ViewModel (as a rule of thumb). All view updating stuff goes into the activity/fragment.
A ViewModel for the example above might look like
public class MyViewModel extends ViewModel {
private MutableLiveData<List<User>> users;
public LiveData<List<User>> getUsers() {
if (users == null) {
users = new MutableLiveData<List<Users>>();
loadUsers();
}
return users;
}
private void loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
This often means that click events should probably be passed down into the ViewModel so that it can massage the data. And the view will just react to the updated (massaged) data.

How onCleared works AndroidViewModel [android-architecture-component]

i am using AndroidViewModel and returning stream of data be it Observable or LiveData, so far its going well, i see there is a method in ViewModel class, onCleared() document says
This method will be called when this ViewModel is no longer used and
will be destroyed.It is useful when ViewModel observes some data and
you need to clear this subscription to prevent a leak of this
ViewModel.
I have a scenario where i return Single<ApiResponse> from retrofit do some .map() in ViewModel and return response as Single<ToBeShownOnUiResponse> i subscribe this in View i.e Fragment.
I add subscriber to CompositeDisposable and thereafter clear it onStop fragment. When i navigate from LoginActivity(hold signin/signup/passwordreset fragment) to HomeActivity(hold tablayout with other fragments) i dont see the logs written in onCleared() method of ViewModel class. Is something wrong i am doing or i did a complete mess of it.
My query here is that in what way onCleared() is helpful to me. What separate code or cleanup i should be writing in it?
Usage:
When i need string resource then i use AndroidViewModel(Formatting some api response according to string resource present in xml) and when only api call require i use ViewModel.
One example use case of calling onCleared is when you use a ViewModel for in-app billing. In that case, it is natural to let the BillingClient to persist as long as an activity (In the below example I used Application), for example like this:
class BillingViewModel(application: Application)
: AndroidViewModel(application), PurchasesUpdatedListener, BillingClientStateListener {
private lateinit var playStoreBillingClient: BillingClient
init {
playStoreBillingClient = BillingClient.newBuilder(application.applicationContext)
.enablePendingPurchases()
.setListener(this).build()
playStoreBillingClient.startConnection(this)
}
...
override fun onCleared() {
super.onCleared()
playStoreBillingClient.endConnection()
}

Categories

Resources