Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android MVVM - How to make LiveData emits the data it has (forcing to trigger the observer)

I have this ViewModel that gets a list from the network and I populate a RecyclerView with the data (MyAvailabilityRepository returns a MutableLiveData, that's why i'm using Transformations.switchMap):

class MyAvailabilityViewModel : ViewModel() {
    private val getListsParams = MutableLiveData<String>()
    private val getListsObservable = Transformations.switchMap(getListsParams) { 
        organizationId -> MyAvailabilityRepository.getSectionedLists(organizationId) 
    }

    fun getListsObservable() : LiveData<Resource<MutableList<SectionedAvailabilityList>>> {
        return getListsObservable
    }

    fun fetchLists(organizationId: String, forceRefresh: Boolean = false) {
        if (getListsParams.value == null || forceRefresh) {
            getListsParams.value = organizationId
        }
    }
}

Fragment's onActivityCreated:

override fun onActivityCreated(savedInstanceState: Bundle?) {
    ...
    viewModel.getListsObservable().observe(this, Observer { // populate RecyclerView })
    viewModel.fetchLists(organizationId)
}

Since getListParams.value is null the first time, it will set getListsParams.value = organizationId and trigger the switchMap and call the repository to get the list from the network.

When I want to force a refresh (by pull-to-refresh) and call the network again, I can use forceRefresh = true:

override fun onRefresh() {
    viewModel.fetchLists(organizationId, forceRefresh = true)
}

It will set the value of organizationId and trigger the Transformations that will then call network.

But, I have a scenario where I clear the data from my RecyclerView's adapter. If after that, the user click a button, I would like to trigger the observer again so that I re-populate the adapter with the data that the getListsObservable has already fetched. I don't want to call forceRefresh on this one cause i'm sure I already have the data and I would just like to trigger the observer again so that my UI is updated with the existing data. Since getListParams.value is not null at that point, then nothing happens when I call fetchLists(organizationId) later on.

Any idea of how I could achieve that with my current setup?

like image 977
Mathbl Avatar asked Jun 28 '18 00:06

Mathbl


People also ask

Why LiveData Observer is being triggered twice?

The first time it is called upon startup. The second time it is called as soon as Room has loaded the data. Hence, upon the first call the LiveData object is still empty. It is designed this way for good reasons.

Can a ViewModel observe LiveData?

ViewModel allows the app's data to survive configuration changes. In this codelab, you'll learn how to integrate LiveData with the data in the ViewModel . The LiveData class is also part of the Android Architecture Components and is a data holder class that can be observed.

How do you observe LiveData in ViewModel in activity?

Attach the Observer object to the LiveData object using the observe() method. The observe() method takes a LifecycleOwner object. This subscribes the Observer object to the LiveData object so that it is notified of changes. You usually attach the Observer object in a UI controller, such as an activity or fragment.

How do I stop LiveData from observing on Android?

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 .


2 Answers

Yes, the answer given by Hong Duan is perfect. I am just going to extend that answer here,

The better way of doing is having an extension function.

My extension function looks like this,

fun <T> MutableLiveData<T>.forceRefresh() {
    this.value = this.value
}

Caller function looks like this,

mutableLiveDataObject.forceRefresh()

Happy Coding!!

like image 183
Narshim Avatar answered Sep 21 '22 22:09

Narshim


Try removeObservers() and observe() again:

viewModel.getListsObservable().removeObservers(this)
viewModel.getListsObservable().observe(this, Observer { // populate RecyclerView })

because:

If LiveData already has data set, it will be delivered to the observer.

See the docs.

Or maybe you can change the getListsObservable() to return a MutableLiveData, then call setValue manually:

fun loadCurrentData() {
    getListsObservable.value = getListsObservable.value
}
like image 35
Hong Duan Avatar answered Sep 23 '22 22:09

Hong Duan