The Bat
The Bat

Reputation: 1115

Android ViewModel MutableLiveData update multiple times

Scenario

Hi, I have an Activity with a ViewPager. In the ViewPagerAdapter, I create instances of a same fragment with different data. And in each instance I initialize a ViewModel

val dataViewModelFactory = this.activity?.let { DataViewModelFactory(it) }
mainViewModel = ViewModelProviders.of(this, dataViewModelFactory).get(MainViewModel::class.java)

In my fragment, I observe two MutableLiveData when I call APIs

mainViewModel.isResponseSuccessful.observe(this, Observer { it ->

        if(it) {
            //do Something
        }else{
            Toast.makeText(activity, "Error in Sending Request", Toast.LENGTH_SHORT).show()
        }


    })

    mainViewModel.isLoading.observe(this, Observer {
        if (it) {
            println("show progress")
        } else {
            println("dismiss progress")
        }
    })

In each fragment, on a button click I load another fragment. And if required call and API to fetch data.

PROBLEM

The code comes to the observe block multiple times in my fragment. When I comeback from one fragment to previous fragment, even though no API is called, the code on observe block is executed.

What I tried

I tried using an activity instance in the ViewModel initialization

mainViewModel = ViewModelProviders.of(activity,dataViewModelFactory).get(MainViewModel::class.java)

But it did not work.

Please help,

Upvotes: 1

Views: 2231

Answers (3)

SSaeedHoseini
SSaeedHoseini

Reputation: 1

If you want to prevent multiple calls of your observer just use distinctUntilChanged before observer.

mainViewModel.isResponseSuccessful.distinctUntilChanged().observe(this, Observer { it ->
        if(it) {
            //do Something
        }else{
            Toast.makeText(activity, "Error in Sending Request", Toast.LENGTH_SHORT).show()
        }
    })

mainViewModel.isLoading.distinctUntilChanged().observe(this, Observer {
        if (it) {
            println("show progress")
        } else {
            println("dismiss progress")
        }
})

Upvotes: 0

aminography
aminography

Reputation: 22832

It might help you:

import java.util.concurrent.atomic.AtomicBoolean

class OneTimeEvent<T>(
    private val value: T
) {

    private val isConsumed = AtomicBoolean(false)

    private fun getValue(): T? =
        if (isConsumed.compareAndSet(false, true)) value
        else null

    fun consume(block: (T) -> Unit): T? =
        getValue()?.also(block)
}

fun <T> T.toOneTimeEvent() =
    OneTimeEvent(this)

First, when you want to post a value on LiveData, use toOneTimeEvent() extension function to wrap it in a OneTimeEvent:

liveData.postValue(yourObject.toOneTimeEvent())

Second, when you are observing on the LiveData, use consume { } function on the delivered value to gain the feature of OneTimeEvent. You'll be sure that the block of consume { } will be executed only once.

viewModel.liveData.observe(this, Observer {
            it.consume { yourObject ->
                // TODO: do whatever with 'yourObject'
            }
        })

In this case, when the fragment resumes, your block of code does not execute again.

Upvotes: 2

If you want to prevent multiple calls of your observer u can just change MutableLiveData to SingleLiveEvent. Read this

Upvotes: 1

Related Questions