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.
Related
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.
So, I have implemented single activity with multiple fragments pattern using Navigation. I used viewmodel for each fragment for non-ui operatios.
The problem is when you navigate using findNavController().navigate(), the fragment is not actually destroyed. Only the onDestroyView is called. So, the fragment's onDestroy never gets called and subsequently the viewmodel doesn't get cleared and so the LiveData observer also remains alive and when i come back to the fragment the observer is created again and so live data is observed twice. Once with the old data that it holds and second with the new data from some operations.
For example,
I have fragmentA and fragmentB
A shows a list and B you can add something which will be shown in the list. maybe fetching new data from api in fragment B to be shown in A.
So, when i go back from fragment B to A the observer gets called twice first with old data then second with the updated data. In the end the list shows correct data but I don't want two observers happening.
I have followed this article
https://medium.com/#BladeCoder/architecture-components-pitfalls-part-1-9300dd969808
and tried to use viewLifeCycleOwner instead of this but that does not help and issue still exists.
I also tried removing observer before observing :
vm.ld.removeObservers(this)
vm.ld.observe(viewLifeCyclerOwner, observer)
still the issue remains.
(I tried removing observer in onDestroyView also, still the issue remains.)
The only work around i found is manually calling the viewmodel onCleared in onDestroyView and clearing the livedata.
In fragment onDestroyView
vm.clear()
In viewmodel
fun clear() = onCleared()
override fun onCleared() {
//do stuff
}
Now, this solves my issue. But i feel this is not a solid solution and there can be a better way to do this. I would be glad if anybody can shed a light on this one. Thanks.
I wasted a couple of days troubleshooting a similar issue. Double check how your VM is initialized:
val myVm: MyViewModel by activityViewModels()
vs.
val myVm: MyViewModel by viewModels()
If you use the by activityViewModels() delegate, you are instructing Android to tie the lifetime of the VM to the host activity rather than the current Fragment. Thus, your VM won't be cleared even when the fragment is destroyed. I learned this the hard way. Switching back to the by viewModels() delegate leaves the VM scoped to the lifetime of the Fragment. When the fragment is destroyed, if it's the only observer, the VM will clear.
I was totally confused by the this vs. viewLifecycleOwner target when observing. Apparently, the choice of target has only to do with whether you intend to manually control presenting the dialog of a DialogFragment. Another gem of confusion.
In your case, if you're switching between fragments and onDestroy isn't being called, it could also be because fragments are being retained. For example, ViewPager2 has offscreenPageLimit, which instructs Android to keep hidden fragments in memory as you switch pages, adding even further to this mess of having to know absolutely everything about everything just to use the SDK.
You can use viewModelStore.clear() in your fragment, and then override onCleared() in ViewModel for dispose what your need
You can set the value of your livedata to null in onDestroyView.
example :
override fun onDestroyView() {
super.onDestroyView()
vm.ld.value = null
}
or
in ViewModel
fun resetLiveData() {
_ld.value = null
}
in Fragment
override fun onDestroyView() {
super.onDestroyView()
vm.resetLiveData()
}
We utilize LiveData's observe method quite a bit in our fragments. A recent release of the androidx fragment sdk is leading to Android Studio marking instances of liveDataObject.observe(this) as incorrect in favor of liveDataObject.observe(getViewLifecycleOwner()) .
Added a new Lint check that ensures you are using getViewLifecycleOwner() when observing LiveData from onCreateView(), onViewCreated(), or onActivityCreated(). (b/137122478)
https://developer.android.com/jetpack/androidx/releases/fragment
We are worried about implementing this change because we do not understand how the functionality of getViewLifecycleOwner() compares to that of using this, and whether it will cause a conflict with the use of this or this.getActivity() when setting up the ViewModel in the fragment.
Additionally, we use the Android Navigation component and noticed that when the user navigates to different fragments within the same activity, each fragment's onDestroyView() method is called, but not onDestroy()
Here is an example of our code in onViewCreated()
vm.getStemLengths().observe(this, stemLengths -> {
this.stemLengths = new ArrayList<>(Stream.of(stemLengths).map(stemLength ->
new SearchModel(Integer.toString(stemLength.getValue()))).toList());
});
Later, in onDestroyView()
vm.getStemLengths().removeObservers(this);
At the same time, depending on the fragment, the ViewModel that houses the LiveData is set up with one of the following:
vm = new ViewModelProvider(this.getActivity()).get(PrepareVM.class);
To persist the viewmodel across fragments in the activity.
Or:
vm = new ViewModelProvider(this).get(AprobacionVM.class);
If the VM does not need to be persisted outside of the current fragment
So to summarize, will changing this to getViewLifeCycleOwner() when observing LiveData objects in fragments' onCreateView() conflict with the ViewModel patterns / Navigation component? Could there be an instance for example where the livedata change ends up triggering an observer from a previous fragment in the same activity that a user navigates away from?
From the documentation of getViewLifeCycleOwner it seems that making this change may allow us to remove the removeObservers() call in each fragment's onDestroyView(). Is that the correct understanding?
Fragment implements LifecycleOwner which maps its create and destroy events to the fragment's onCreate and onDestroy, respectively.
Fragment.getViewLifecycleOwner() maps its create and destroy events to the fragment's onViewCreated and onDestroyView, respectively. The precise sequence is described here.
If you're working with views in your observers, you'll want the view lifecycle. Otherwise you might get updates when the view hierarchy is invalid, which may lead to crashes.
From the documentation of getViewLifeCycleOwner it seems that making this change may allow us to remove the removeObservers() call in each fragment's onDestroyView().
Correct.
There is already a bug with it. If you do a new Intent for a fragment already use it gives getView is null error. Why? The activity is newly creared.
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.
If we scope LiveData to the lifecycle of a fragment by passing this to the observe method, the fragment doesn't get immediate updates if it is only detached from the activity, but not removed, because only the view is destroyed, not the fragment instance itself.
Instead, we can scope LiveData to the lifecycle of the view of the fragment, by calling observe in onActivityCreated and passing getViewLifecycleOwner() rather than this.
Is there any reason to not scope the LiveData to the fragment's view?
If your fragment does not have a UI then you will need to scope it with fragment's lifecycle.
Many people/libraries used and still use headless(UI-Less) worker fragments to have lifecycle awareness to safely pass on asynchronously retrieved data (Network Call) to the UI. (Similar to what Loaders do)