Is it possible to prevent LiveData receive the last value when start observing? I am considering to use LiveData as events.
For example events like show message, a navigation event or a dialog trigger, similar to EventBus.
I found something similar SingleLiveEvent - but it work only for 1 observer and not for multiple observers.
I created a new Class that will hold my real data and a "special ID":
class LiveDataItem {
long mRealtimeNanos;
YOUR_PREVIOUS_LIVEDATA_TYPE mData;
LiveDataItem(YOUR_PREVIOUS_LIVEDATA_TYPE data, long realtimeNanos) {
this.mRealtimeNanos = realtimeNanos;
this.mData = data;
}
}
Then I created a new "global" variable:
final List<Long> mExcludedRealtimeNanos = new ArrayList<>;
At this point I choose to "set/postValue()" of my "LiveDataItem" type instead of the original "YOUR_PREVIOUS_LIVEDATA_TYPE" type via a new and custom "postValue()" method:
public void myPostValue(YOUR_PREVIOUS_LIVEDATA_TYPE data, boolean notifyWhenObserved) {
long cRealtimeNanos = SystemClock.realtimeNanos();
if (!notifyWhenObserved) mExcludedRealtimeNanos.add(cRealtimeNanos);
....postValue(new LiveDataItem(data, cRealtimeNanos));
}
Then I created a normal Observer that will receive all "Changed()" events and inside it I put a check about "RealtimeNanos":
public void onChanged(LiveDataItem myDataItem) {
boolean cFound = false;
for (Long cRealtimeNanos : mExcludedRealtimeNanos) {
if (cRealtimeNanos == myDataItem.mRealtimeNanos) {
cFound = true;
break;
}
}
//check if it was found --> NO: it means that I wish to get the notification
if (!cFound) mMyOnChangedCallback(myDataItem.mData)
}
Obliviously the "mMyOnChangedCallback()" method is a callback function that will be called whenever the original "onChanged()" event is raised BUT only if you set to notify it during data creation time.
You can choose to be notified again just by removing THAT RealtimeNanos from "mExcludedRealtimeNanos" and then attach a new Observer to that LiveData.
Few changes could improve this code but I wrote you what I remember of my old code (I'm currently away from my computer at this moment). For example we can decide to remove a value from "mExcludedRealtimeNanos" when a new data is posted using our custom postValue() method....
I don't think it is possible to prevent LiveData from receiving the last value when start observing if you are using them as it is. What you can do is to extend ViewModel
class and make it notify the view only if the observer is added.
Another option is to simply ignore the callback.
Add a flag to the ViewModel.
private boolean isFirstTime = true; public boolean getIsFirstTime() { return isFirstTime; } public boolean onObserverAdded() { isFirstTime = false; }`
Add checking in the callback
@Override public void onChanged(@Nullable final String newName) { boolean ignore = ((MyViewModel)ViewModelProviders.of(MyActivity.this).get(MyViewModel.class)).getIsFirstTime(); if(ignore) return; // Update the UI }
Finally call
onObserverAdded()
after observer is added.
Having some experience with RxJava, I've grown accustomed to thinking that such behavioral requirements are typically a concern of the Observeable
(LiveData
in our case). There are many operators such as replay(), that can control what's actually emitted (and when) compared to the actual publishes made by the user. In essence, SingleLiveEvent
has the same notion to it, too.
I therefore came up with this modified implementation of MutableLiveData
called VolatileLiveData
:
open class VolatileLiveData<T> : MutableLiveData<T>() {
private val lastValueSeq = AtomicInteger(0)
private val wrappers = HashMap<Observer<in T>, Observer<T>>()
@MainThread
public override fun setValue(value: T) {
lastValueSeq.incrementAndGet()
super.setValue(value)
}
@MainThread
public override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
val observerWrapper = ObserverWrapper(lastValueSeq, observer)
wrappers[observer] = observerWrapper
super.observe(owner, observerWrapper)
}
@MainThread
public override fun observeForever(observer: Observer<in T>) {
val observerWrapper = ObserverWrapper(lastValueSeq, observer)
wrappers[observer] = observerWrapper
super.observeForever(observerWrapper)
}
@MainThread
public override fun removeObserver(observer: Observer<in T>) {
val observerWrapper = wrappers[observer]
observerWrapper?.let {
wrappers.remove(observerWrapper)
super.removeObserver(observerWrapper)
}
}
}
private class ObserverWrapper<T>(private var currentSeq: AtomicInteger, private val observer: Observer<in T>) : Observer<T> {
private val initialSeq = currentSeq.get()
private var _observer: Observer<in T> = Observer {
if (currentSeq.get() != initialSeq) {
// Optimization: this wrapper implementation is only needed in the beginning.
// Once a valid call is made (i.e. with a different concurrent sequence), we
// get rid of it any apply the real implementation as a direct callthrough.
_observer = observer
_observer.onChanged(it)
}
}
override fun onChanged(value: T) {
_observer.onChanged(value)
}
}
First, similar to @emandt, I've associate unique sequences to each live value -- but strictly in the scope of the live data itself. This sequence is set whenever a value is set to the live data.
Second, inspired by SingleLiveData
, I've introduced wrappers around the user's observer that only call through to it if the sequence is different (i.e. a new value has been set since subscription was made).
That basically sums it up, but for full documentation, please head over to my gist.
Usage
As for using it - if you have full control over the LiveData
, simply use VolatileLiveData
as you would use MutableLiveData
. If the data originally comes from somewhere else (e.g. Room), Transformations.switchMap()
can be used so as to make a 'switch' to the volatile implementation.
Faced the same problem, and I created some simple kotlin extention functions which can solve the problem easily.
Usage as below:
val liveData = MutableLiveData<String>()
liveData.value = "Hello"
val freshResult = mutableListOf<String>()
val normalResult = mutableListOf<String>()
liveData.observeForeverFreshly(Observer {
freshResult.add(it)
})
liveData.observeForever(Observer {
normalResult.add(it)
})
liveData.value = "World"
assertEquals(listOf("World"), freshResult)
assertEquals(listOf("Hello", "World"), normalResult)
Basic source code is explained as bllow.
For some more detail (to support some special situations for example MediatorLiveData
returned from Transformations.map), you can view it in github : livedata-ext
FreshLiveData.kt
fun <T> LiveData<T>.observeFreshly(owner: LifecycleOwner, observer: Observer<in T>) {
// extention fuction to get LiveData's version, will explain in below.
val sinceVersion = this.version()
this.observe(owner, FreshObserver<T>(observer, this, sinceVersion))
}
fun <T> LiveData<T>.observeForeverFreshly(observer: Observer<in T>, skipPendingValue: Boolean = true) {
val sinceVersion = this.version()
this.observeForever(FreshObserver<T>(observer, this, sinceVersion))
}
// Removes the observer which has been previously observed by [observeFreshly] or [observeForeverFreshly].
fun <T> LiveData<T>.removeObserverFreshly(observer: Observer<in T>) {
this.removeObserver(FreshObserver<T>(observer, this, 0))
}
class FreshObserver<T>(
private val delegate: Observer<in T>,
private val liveData: LiveData<*>,
private val sinceVersion: Int
) : Observer<T> {
override fun onChanged(t: T) {
if (liveData.version() > sinceVersion) {
delegate.onChanged(t)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as FreshObserver<*>
if (delegate != other.delegate) return false
return true
}
override fun hashCode(): Int {
return delegate.hashCode()
}
}
Becasue we need to access LiveData's pcakage visibile methond getVersion()
for comparasion, so create a class in package android.arch.lifecycle
or androidx.lifecycle
(AndroidX):
LiveDataHiddenApi.kt
package androidx.lifecycle
fun LiveData<*>.version(): Int {
return this.getVersion()
}
I`m using this EventWraper class from Google Samples inside MutableLiveData
/**
* Used as a wrapper for data that is exposed via a LiveData that represents an event.
*/
public class Event<T> {
private T mContent;
private boolean hasBeenHandled = false;
public Event( T content) {
if (content == null) {
throw new IllegalArgumentException("null values in Event are not allowed.");
}
mContent = content;
}
@Nullable
public T getContentIfNotHandled() {
if (hasBeenHandled) {
return null;
} else {
hasBeenHandled = true;
return mContent;
}
}
public boolean hasBeenHandled() {
return hasBeenHandled;
}
}
In ViewModel :
/** expose Save LiveData Event */
public void newSaveEvent() {
saveEvent.setValue(new Event<>(true));
}
private final MutableLiveData<Event<Boolean>> saveEvent = new MutableLiveData<>();
LiveData<Event<Boolean>> onSaveEvent() {
return saveEvent;
}
In Activity/Fragment
mViewModel
.onSaveEvent()
.observe(
getViewLifecycleOwner(),
booleanEvent -> {
if (booleanEvent != null)
final Boolean shouldSave = booleanEvent.getContentIfNotHandled();
if (shouldSave != null && shouldSave) saveData();
}
});
Even i had the same requirement. I have achieved this by extending the MutableLiveData
package com.idroidz.android.ion.util;
import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.Observer;
import android.support.annotation.MainThread;
import android.support.annotation.Nullable;
import java.util.concurrent.atomic.AtomicBoolean;
public class VolatileMutableLiveData<T> extends MutableLiveData<T> {
private final AtomicBoolean mPending = new AtomicBoolean(false);
@MainThread
public void observe(LifecycleOwner owner, final Observer<T> observer) {
// Observe the internal MutableLiveData
mPending.set(false);
super.observe(owner, new Observer<T>() {
@Override
public void onChanged(@Nullable T t) {
if (mPending.get()) {
observer.onChanged(t);
}
}
});
}
@MainThread
public void setValue(@Nullable T t) {
mPending.set(true);
super.setValue(t);
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
public void call() {
setValue(null);
}
public void callFromThread() {
super.postValue(null);
}
}
来源:https://stackoverflow.com/questions/49832787/android-livedata-prevent-receive-the-last-value-when-subscribe