Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ViewModel not getting cleared in Navigation navigate and live data in viewmodel stays alive

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.

like image 471
Ron Daulagupu Avatar asked Dec 17 '19 11:12

Ron Daulagupu


People also ask

Why does ViewModel survive configuration changes?

Retain an object during a configuration change In such a situation, you can alleviate the burden of reinitializing part of your activity by using a ViewModel . ViewModel objects are preserved across configuration changes so they are the perfect place to keep your UI data without having to query them again.

How does a ViewModel retain itself?

ViewModel objects are automatically retained during configuration changes so that data they hold is immediately available to the next activity or fragment instance. FYI: You can use ViewModel to preserve UI state only during a configuration change, nothing else as explained perfectly in this official doc.

Is ViewModel destroyed when activity destroy?

It is because of the Android system destroys the activity and recreates it from scratch, and as the activity is recreated, the old resources and instances of various classes(ViewModelProvider in our case) are first destroyed on calling onDestroy() method and later recreated in onCreate() .

Does ViewModel hold data?

ViewModel is responsible for holding and processing all the data needed for the UI. It should never access your view hierarchy (like view binding object) or hold a reference to the activity or the fragment.


1 Answers

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.

like image 97
rmirabelle Avatar answered Oct 23 '22 10:10

rmirabelle