20

I observed that MutableLiveData triggers onChanged of an observer even if the same object instance is provided to its setValue method.

//Fragment#onCreateView - scenario1
val newValue = "newValue"
mutableLiveData.setValue(newValue) //triggers observer
mutableLiveData.setValue(newValue) //triggers observer

//Fragment#onCreateView - scenario2
val newValue = "newValue"
mutableLiveData.postValue(newValue) //triggers observer
mutableLiveData.postValue(newValue) //does not trigger observer

Is there a way to avoid an observer be notified twice if the same or an equivalent instance is provided to setValue()/postValue()

I tried extending MutableLiveData but that did not work. I could be missing something here

class DistinctLiveData<T> : MutableLiveData<T>() {

    private var cached: T? = null

    @Synchronized override fun setValue(value: T) {
        if(value != cached) {
            cached = value
            super.setValue(value)
        }
    }

    @Synchronized override fun postValue(value: T) {
        if(value != cached) {
            cached = value
            super.postValue(value)
        }
    }
}
Pravin Sonawane
  • 1,803
  • 4
  • 18
  • 32

5 Answers5

46

There is already in API : Transformations.distinctUntilChanged()

distinctUntilChanged

public static LiveData<X> distinctUntilChanged (LiveData<X> source)

Creates a new LiveData object does not emit a value until the source LiveData value has been changed. The value is considered changed if equals() yields false.

<<snip remainder>>

dbc
  • 104,963
  • 20
  • 228
  • 340
Jurij Pitulja
  • 5,546
  • 4
  • 19
  • 25
  • 2
    Since this got flagged as a possible link-only answer, I excerpted a bit of the documentation. – dbc Mar 24 '19 at 21:13
  • 2
    Worth mentioning, that `Transformations.distingUntilChanged()` is available earliest in Ver 2.1.0 which is not yet a stable release! https://developer.android.com/jetpack/androidx/releases/lifecycle#2.1.0-alpha01 – ASP Jul 29 '19 at 15:11
  • 1
    update: It is now in the stable release. safe to use. – Nino van Hooff Mar 30 '20 at 16:45
14

You can use the following magic trick to consume "items being the same":

fun <T> LiveData<T>.distinctUntilChanged(): LiveData<T> = MediatorLiveData<T>().also { mediator ->
    mediator.addSource(this, object : Observer<T> {
        private var isInitialized = false
        private var previousValue: T? = null

        override fun onChanged(newValue: T?) {
            val wasInitialized = isInitialized
            if (!isInitialized) {
                isInitialized = true
            }
            if(!wasInitialized || newValue != previousValue) {
                previousValue = newValue
                mediator.postValue(newValue)
            }
        }
    })
}

If you want to check referential equality, it's !==.


But it has since been added to Transformations.distinctUntilChanged.

EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
3

If we talk about MutableLiveData, you can create a class and override setValue and then only call through super if new value != old value

class DistinctUntilChangedMutableLiveData<T> : MutableLiveData<T>() {
    override fun setValue(value: T?) {
        if (value != this.value) {
            super.setValue(value)
        }
    }
}
Kuno
  • 3,492
  • 2
  • 28
  • 44
  • 1
    not sure why this did not get more upvotes, seems the simplest and cleanest way imo. – bastami82 Sep 18 '20 at 16:56
  • Yes, you should know how equality works, does not make the answer wrong though. – Kuno Sep 28 '20 at 15:39
  • 1
    @bastami82: Please stop changing my answer. Correct my typos, add some formatting, that's fine, for anything else, write your own answer. – Kuno Oct 03 '20 at 14:08
  • 2
    just tried to be helpful as my suggested changes did NOT change the essence of your answer, I could have written my own answer you are right, but I thought given all credit to your answer, sadly you did not appreciate the effort (by the way this is how code evolves). In my view suggested changes made it clearer, I only thought this is a good answer and need a bit of polishing, disappointed you did not like it but I respect your decision. – bastami82 Oct 05 '20 at 07:58
1

In my case I have quite complex objects which I have to compare by some fields. For this I've changed EpicPandaForce's answer:

fun <T> LiveData<T>.distinctUntilChanged(compare: T?.(T?) -> Boolean = { this == it }): LiveData<T> = MediatorLiveData<T>().also { mediator ->
    mediator.addSource(this) { newValue ->
        if(!newValue.compare(value)) {
            mediator.postValue(newValue)
        }
    }
}

By default it uses standard equals method, but if you need - you can change distinction logic

Andrii Turkovskyi
  • 27,554
  • 16
  • 95
  • 105
0

I don't use the MediatorLiveData, i just wrapper my observer, and record and compare it. it work well in my livedata-Bus

class DiffObserver<X>(var observer: Observer<X>) : Observer<X>{

    var mFirstTime = true
    var previousValue : X? = null

    override fun onChanged(currentValue: X?) {
        if (mFirstTime || (previousValue == null && currentValue != null) ||
                (previousValue != null && currentValue == null) ||
                previousValue?.equals(currentValue) != true) {
            mFirstTime = false
            observer.onChanged(currentValue)
        } 
        previousValue = currentValue
    }

}