7

I'm struggling with a LiveData observer which is firing twice. In my fragment I'm observing a LiveData as below, using viewLifeCycleOwner as LifeCycleOwner

private lateinit var retailViewModel: RetailsViewModel

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        retailViewModel =  ViewModelProviders.of(this).get(RetailsViewModel::class.java)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

  retailViewModel.retailLiveData.observe(viewLifecycleOwner, Observer {
    // updating UI here, but firing twice!
  }

  retailViewModel.getRetailById(retail.id)
} 

And this is my view model:

class RetailsViewModel(override val service: MyFoodyApiService = MyFoodyApiService.service) :
    BaseViewModel(service) {

    var retailLiveData: MutableLiveData<Retail> = MutableLiveData()

    fun getRetailById(id: Int) {
        scope.launch {
            try {
                val response =
                    service.getRetailById(authString, id).await()
                when (response.isSuccessful) {
                    true -> {
                        response.body()?.let { payload ->
                            retailLiveData.postValue(payload.data)
                        } ?: run {
                            errorLiveData.postValue("An error occurred: ${response.message()}")
                        }
                    }
                    false -> errorLiveData.postValue("An error occurred: ${response.message()}")
                }
            } catch (e: Exception) {
                noConnectionLiveData.postValue(true)
            }
        }
    }

}

When I run the fragment for the first time, everything works fine, however when I go to its DetailFragment and come back, retailLiveData Observer callback is fired twice. According to this article this was a known problem solved with the introduction of viewLifeCycleOwner who should be helpful to remove active observers once fragment's view is destroyed, however it seems not helping in my case.

Nicola Gallazzi
  • 7,897
  • 6
  • 45
  • 64

1 Answers1

6

This happens because view model retains value when you open another fragment, but the fragment's view is destroyed. When you get back to the fragment, view is recreated and you subscribe to retailLiveData, which still holds the previous value and notifies your observer as soon as fragment moves to started state. But you are calling retailViewModel.getRetailById(retail.id) in onViewCreated, so after awhile the value is updated and observer is notified again.

One possible solution is to call getRetailById() from view model's init method, the result will be cached for view model lifetime then.

esentsov
  • 6,372
  • 21
  • 28
  • Thanks @esentov this could be a solution, however if I call getRetailById() before adding subscribers (I'm thinking about the first run of the fragment) am I going to receive the callback in the same way? – Nicola Gallazzi Aug 09 '19 at 17:28
  • You'll receive callback only once, because `retailLiveData` does not have initial value, so the observer will not be notified when it's added. It will be notified only when value is actually set to live data. – esentsov Aug 09 '19 at 17:32