11

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.

Ron Daulagupu
  • 423
  • 6
  • 18

3 Answers3

6

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.

Dharman
  • 30,962
  • 25
  • 85
  • 135
rmirabelle
  • 6,268
  • 7
  • 45
  • 42
0

You can use viewModelStore.clear() in your fragment, and then override onCleared() in ViewModel for dispose what your need

kovac777
  • 730
  • 6
  • 19
0

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()
}