6

I am using Live Data to publish states from View Model to Fragments, this might result in states getting published frequently. But the Mutable Live Data is skipping the initial values and taking the latest value available.

There is an article which talks about this characteristic, but is there a way of handling this case, such as Flowable in RxJava or setting Back Pressure Strategy or will I need to go back to using RxJava and handle Life-cycle based publishing?

Following is a sample code which shows this behaviour. Values from 1 to 10 are published but only two values are received, 0 and 10. Can we change this behaviour in Live Data or should I use RxJava for this purpose?

Fragment (Subscriber) :

class ParentFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        viewModel = ViewModelProviders.of(
            this, ParentViewModelFactory(this, null)
        ).get(ParentViewModel::class.java)

        viewModel.fastLiveData.observe(this, Observer {
            Timber.i(it.toString())
        })

        viewModel.startPublishing()
    }
}

View Model (Publisher):

class ParentViewModel(private val savedState : SavedStateHandle)
    : ViewModel<ParentState>() {

    val fastLiveData : MutableLiveData<Int> = MutableLiveData(0)

    fun startPublishing() {
        for(x in 1..10) {
            Timber.i(x.toString())
            fastLiveData.postValue(x)
        }
    }
}

Output :

(ParentViewModel.kt:30)#startPublishing: 1
(ParentViewModel.kt:30)#startPublishing: 2
(ParentViewModel.kt:30)#startPublishing: 3
(ParentViewModel.kt:30)#startPublishing: 4
(ParentViewModel.kt:30)#startPublishing: 5
(ParentViewModel.kt:30)#startPublishing: 6
(ParentViewModel.kt:30)#startPublishing: 7
(ParentViewModel.kt:30)#startPublishing: 8
(ParentViewModel.kt:30)#startPublishing: 9
(ParentViewModel.kt:30)#startPublishing: 10
(ParentFragment.kt:57)#onChanged: 0
(ParentFragment.kt:57)#onChanged: 10
ashwin mahajan
  • 1,654
  • 5
  • 27
  • 50

3 Answers3

7

The reason is you are using postValue()

  • postValue will set the value on background thread, and it will set the latest value in case of a lot of emission.
  • setValue will set your value on main thread

IF you will change to fastLiveData.setValue(x) you will get the behaviour you want.

Mustafa Ozhan
  • 321
  • 6
  • 9
  • 3
    This can mislead to understand. `postValue` can be used on background, and value will be set on main thread asynchronously. `setValue` will set value immediately on current thread but current thread *should be* main thread. – khcpietro Nov 11 '20 at 02:21
  • Is there any chances that even using setValue we face skipping value – Shubham AgaRwal Aug 20 '21 at 06:51
  • @ShubhamAgaRwal I don't think so. I have non-standard use scenario in UI-heavy application that sometimes has problems with drawing many ListView items and all changes made by setValue are delivered. To be sure you can look at the setValue and postValue implementations in source code. – Kamil Oct 19 '22 at 00:55
2

Coroutines bring a solution to this:

Example:

events.value = "Event A"
events.value = "Event B"
events.value = "Event C"

This, indeed only populates "Event C" to the observer.

However, if you wrap your code with coroutine, it's gonna work:

viewModelScope.launch(Dispatchers.Main) {
    events.value = "Event A"
    events.value = "Event B"
    events.value = "Event C"
}
Jacek Kwiecień
  • 12,397
  • 20
  • 85
  • 157
  • This worked for me. The Activity/Fragment was not receiving all the events sent when using postValue(X). But with this method, all events are received on the view. – R. Campos May 24 '21 at 19:57
1

Use RxJava. LiveData is meant to be observed in the view layer by main thread. It does not make sense for LiveData observers to consume more than screen refresh rate or human eye can handle.

Using both RxJava and LiveData in a single project is a legitimate design choice. There should be several articles and code samples on the Internet, here is one I have just found: https://proandroiddev.com/mvvm-architecture-using-livedata-rxjava-and-new-dagger-android-injection-639837b1eb6c

Sanlok Lee
  • 3,404
  • 2
  • 15
  • 25
  • Can you please provide a references for this : "It does not make sense for LiveData observers to consume more than screen refresh rate or human eye can handle" ? – ashwin mahajan Oct 15 '19 at 09:37
  • The statement applies to Android in general, not just for `LiveData`. If you haven't noticed, `LiveData` observers are invoked in ui thread only--to update ui. So any excessive ui updating will be either wasted or block the ui thread, and hence, it does not make sense for livedata observers to consume more than ui thread can handle. `LiveData` ensures eventual consistency between ui and data, but doesn't say that notifications will be buffered. – Sanlok Lee Oct 15 '19 at 17:44
  • Same thing applies to `RxJava` as well. Even if you use `RxJava`, high frequency work should be done in ViewModel with a worker thread, and avoid dispatching too frequently to view layer. Still if you are willing to sacrifice the performance, you could simply use `LiveData#setValue` instead of `LiveData#postValue` but I was guessing that's something not you wanted.. – Sanlok Lee Oct 15 '19 at 17:48
  • In my case actually there was a sense to observe it on main thread. I'm using LiveData to pass onKeyDown from MainActivity to my ViewModel in a Fragment thru BroadcastReceiver. My interface sometimes is struggling with filling ListView and it was skipping some keystrokes. – Kamil Oct 19 '22 at 00:50