Can ViewModel implement LifecycleOwner? - android

I know that, ViewModels should never keep a reference to a LifecycleOwner since it keeps a reference to a context and hence this causes a memory leak. But my question is, can I implement a LifecyclerOwner for ViewModel itself? with two valid callbacks, onCreate() triggered when view model's constructor is invoked and onDestroy() triggered when view model's onCleared() is invoked. Since LifecycleOwner is just an Java interface, I think this will not cause any memory leak. But to be on safer side, I'm asking this.

Related

Why would a LiveData observed with viewLifecycleOwner get callbacks after onDestroyView?

I have a Fragment that’s using View Binding, so it sets its _binding member variable in onCreateView and nulls it out in onDestroyView. In onViewCreated, I observe a LiveData from the view model using viewLifecycleOwner as the LifecycleOwner. (In reality this is split between a fragment and a base class, but I can’t see how that would explain any of this)
I can’t reproduce this in house, but crashlytics is reporting cases in the field where the LiveData’s observer is getting called back when the binding is null, making me think that somehow it’s being called after onDestroyView has been called. Any idea how that’s possible?
UPDATE: Turns out the observer was calling postDelayed on one of the views, so the Runnable was being executed (and accessing the binding) after onDestroyView had been called.
Thanks to #Zain and #EpicPandaForce for taking a look.

Clarification about LiveData's strong references in ViewModels

The ViewModel documentation quotes:
Caution: A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context.
And the LiveData documentation states next:
LiveData keeps a strong reference to the observer and the owner as long as the given LifecycleOwner is not destroyed. When it is destroyed, LiveData removes references to the observer & the owner.
So, if we have a LiveData variable defined in a ViewModel, and we set to observe it using a fragment (or activity) as owner and/or observer, does this mean that we are breaking the rule stated in the ViewModel documentation? Does the action of setting such type of owner/observer, indirectly place an Activity context reference within the ViewModel?
By the LiveData documentation seems it shouldn't be a concern as it will take care of removing the references when required, but I would like to know if I am missing something, as seems the documentation is contradicting itself.
I'm planning to write a post to explain about the issue you asked. But I have not published it. You can read my draft blog in this link: Exploring LiveData in Android
Or short answer for you:
LifecycleOwner: is an interface that has only one method: getLifecyle(). This method returns a Lifecycle object.
Lifecycle: is a class that has states and allows other objects to observe its state changes.
Activity and Fragment: implement LifecycleOwner interface. In the getLifecycle() method, we return a LifecycleRegistry object as Lifecycle. The LifecycleRegister class extends Lifecycle class. This class helps Activity and Fragment notify observers about their state change.
ObserverWrapper is an inner class in LiveData. This class will help LiveData know the state of LifecycleOwner and the latest version of data that the Observer is having.
LifecycleBoundObserver is also an inner class in LiveData. This class extends OberverWrapper to provide LiveData the state of LifecycleOwner and keeps Observer and the data’s version that the Observer is having. It also implements LifecycleEventObserver to observe the Lifecyle’state changes ( in our case, it is Activity’s state and Fragment’s state). This class helps LiveData know the state of Activity (Fragment) and the data version that the Observer is having, so LiveData can decide whether it will notify Activity (Fragment) about an update or not.

LiveData observing in Fragment

As of 2019, I'm trying to follow a best practice on where to start observing LiveData in Fragments and if I should pass this or viewLifecycleOwner as a parameter to the observe() method.
According to this Google official documentation, I should observe in onActivityCreated() passing this (the fragment) as parameter.
According to this Google sample, I should observe in onViewCreated() passing viewLifecycleOwner as parameter.
According to this I/O video, I shouldn't use this but instead viewLifecycleOwner, but doesn't specify where should I start observing.
According to this pitfalls post, I should observe in onActivityCreated() and use viewLifecycleOwner.
So, where should I start observing? And should I either use this or viewLifecycleOwner?
If observing from an Activity you can observe on onCreate() and use this for the LifecycleOwner as stated here:
If you have a lifecycle-aware component that is hooked up to the lifecycle of your activity it will receive the ON_CREATE event. The method annotated with #OnLifecycleEvent will be called so your lifecycle-aware component can perform any setup code it needs for the created state.
Now if you are observing within a Fragment you can observe on onViewCreated() or onActivityCreated() and you should use getViewLifecycleOwner() and here is why:
Get a LifecycleOwner that represents the Fragment's View lifecycle. In most cases, this mirrors the lifecycle of the Fragment itself, but in cases of detached Fragments, the lifecycle of the Fragment can be considerably longer than the lifecycle of the View itself.
As in the I/O talk Yigit says, the Fragment and its view has different lifecycles. You would need to identify if your LiveData is related to the fragment or its view and pass the one desired. The compiler will accept both since both are implementations of LifecycleOwner
It doesn't matter whether you do it on onViewCreated or onActivityCreated. Both are called when the fragment is inflated, onViewCreated first, onActivityCreated afterwards. It's really a matter of preference.
The LiveData object takes a LifecycleOwner, and both Fragment and Activity implement the interface, so you just need to pass this.

Will it cause resource leakage if I have a long life LifeCycleOwner keep observing LiveData?

In certain case like Home Widget (AppWidgetProvider), I don't have access to Activity or Fragment.
Usually, I use ProcessLifecycleOwner.get(), or the following LifeCycleOwner to observe LiveData.
public enum ForeverStartLifecycleOwner implements LifecycleOwner {
INSTANCE;
private final LifecycleRegistry mLifecycleRegistry;
ForeverStartLifecycleOwner() {
mLifecycleRegistry = new LifecycleRegistry(this);
mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
}
#NonNull
#Override
public Lifecycle getLifecycle() {
return mLifecycleRegistry;
}
}
In most situation, in the callback of LiveData, I will try to remove LifeCycleOwner from further observing LiveData, by using liveData.removeObserver.
However, there are situation where
LiveData fails to trigger callback.
Hence, I didn't remove LifeCycleOwner from LiveData in LiveData's callback.
In such situation, will it cause resource leakage? For instance, GC notice a long life LifeCycleOwner is observing LiveData A. Although LiveData A is already out of scope, GC will not free-up LiveData A, because a long living LifeCycleObserver still observing it?
If so, how can I resolve this kind of leakage?
will it cause resource leakage?
Answer : I don't think so that this kind of scenario will make memory leak.
Why?
Because, as we can see that once LiveData is out of scope for any LifecyclerOwner, it removes reference if you set your LifecycleOwner at DESTROYED state.
see observe() method documentation;
obesrve() :
Adds the given observer to the observers list within the lifespan of
the given owner. The events are dispatched on the main thread. If
LiveData already has data set, it will be delivered to the observer.
The observer will only receive events if the owner is in STARTED or
RESUMED state (active).
If the owner moves to the DESTROYED state, the observer will
automatically be removed.
When data changes while the owner is not active, it will not receive
any updates. If it becomes active again, it will receive the last
available data automatically.
LiveData keeps a strong reference to the observer and the owner as
long as the given LifecycleOwner is not destroyed. When it is
destroyed, LiveData removes references to the observer & the owner.
If the given owner is already in DESTROYED state, LiveData ignores the
call.
If the given owner, observer tuple is already in the list, the call is
ignored. If the observer is already in the list with another owner,
LiveData throws an IllegalArgumentException.
TL;DR
So, according to above documentation, from line :
If the owner moves to the DESTROYED state, the observer will automatically be removed.
If the given owner is already in DESTROYED state, LiveData ignores
the call.
it's clear that if your LifecycleOwner has no scope (already in DESTROYED state), then LiveData removes it's strong reference thus there's no chance of memory leak.
how can I resolve this kind of leakage? & get callback all the time:
Answer : You've already created your own LifecycleOwner, so I'll suggest you to handle it yourself. make your LiveData to observeForever() & handle removal (removeObserver()) yourself once your LifecycleOwner reaches to DESTROYED state.
this will not cause memory leak. because it's stated in document:
(This means that the given observer will receive all events and will never be automatically removed)
observeForever() :
Adds the given observer to the observers list. This call is similar to
observe(LifecycleOwner, Observer) with a LifecycleOwner, which is
always active. This means that the given observer will receive all
events and will never be automatically removed. You should manually
call removeObserver(Observer) to stop observing this LiveData. While
LiveData has one of such observers, it will be considered as active.
If the observer was already added with an owner to this LiveData,
LiveData throws an IllegalArgumentException.
This will help you receive callback all the time, you just need to handle how you can remove callback once done.
Hope it helps !
In above example, if don’t clear that reference explicitly (as we would usually do in activity/fragments onStop()); there will be a risk of memory leak.

RxJava unsubscribing in RecyclerView adapter presenter

I've got a presenter that is created for the RecyclerView.Adapter. The presenter has a method to call into the API layer, then change some local data values. This is implemented as an Rx chain:
public void doStuff(Object args) {
mRemote.doStuff(args)
.doOnNext(count -> mLocal.setStuffCount(count))
.firstOrError()
.subscribeOn(mSchedulerProvider.io())
.observeOn(mSchedulerProvider.ui())
.subscribe(...)
<...>
This method might be called several times with different arguments, and therefore generate different subscriptions. As I understand, RxJava will unsubscribe onComplete or onError. However, it is possible that there are, for example, 5 subscriptions created, and before they are resolved, the user moves on from the fragment, and therefore the Adapter is no longer relevant and should be collected. However, as I understand, it won't be collected cause of the active subscriptions, until those are resolved.
Normally I link the presenter with the related view, then store all subscriptions in a CompositeDisposable and clear the disposable when the lifecycle suggests it's time to clear (for example, a view goes to the onPause state). I could implement a similar approach here, however that means I would have to:
1) Establish a relationship between a view and the adapter, so that when the view's lifecycle method is triggered, it will take the adapter and invoke an unsubscribe method on it, which will then take the presenter and invoke clear(). Could I rely on onDetachedFromRecyclerView for that? Or would I have to do that manually?
2) Each time my presenter method to do stuff is called, I'll be creating a new subscription, which means the CompositeDisposable will grow in size until who know's what. Although the call has been executed, the disposable is still referenced in the CompositeDisposable.
I have a feeling there's a simpler solution here so hoping someone might point me at it.
EDIT:
Another solution that comes to mind, would be to create a presenter for each ViewHolder and link subscription to the binding and recycling of the holder. However, that means keeping a few more presenters hanging around - but the approach seems a bit cleaner.
Not sure if this is going to be useful to anyone but this is how I've solved this problem.
I ended up merging my Presenters while keeping different View interfaces for the ViewHolder and the Fragment (will call them ViewHolderView and FragmentView).
I liked the idea of having a Presenter for each ViewHolderView, however, it didn't really solve my problem. I was keeping my CompositeDisposable inside the Presenter, but with that approach, I had to move it out to the Adapter level and keep a reference to the Disposable inside my ViewHolder. Similar to the answer suggested here
I didn't like the idea of manipulating the Adapter from the Fragment lifecycle manually as I was already calling subscribe() and unsubscribe() on FragmentView, and onDetachedFromRecyclerView is not called unless you nullify the adapter on the RecyclerView
The solution was to merge my Adapter presenter with my Fragment presenter while keeping the ViewHolderView interface. So I ended up with a contract for one Presenter, and two separate View interfaces, and I'm passing the Presenter to the Adapter. When FragmentView unsubscribes, it cleans up everything.

Categories

Resources