Android : Live Data skipping values
Asked Answered
P

3

7

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
Pectoral answered 14/10, 2019 at 15:34 Comment(0)
I
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.

Instance answered 4/6, 2020 at 7:42 Comment(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.Salol
Is there any chances that even using setValue we face skipping valueGlare
@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.Impel
P
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"
}
Presidentelect answered 12/2, 2021 at 9:16 Comment(1)
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.Seepage
C
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

Charleen answered 14/10, 2019 at 22:28 Comment(4)
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" ?Pectoral
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.Charleen
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..Charleen
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.Impel

© 2022 - 2024 — McMap. All rights reserved.