Is it possible to prevent LiveData
receive the last value when start observing?
I am considering to use LiveData
as events.
For example eve
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 isFirstTime() { 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)).isFirstTime();
if(ignore) return;
// Update the UI
}
Finally call onObserverAdded()
after observer is added.
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....
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);
}
}
There is no reason to use LiveData as something that it is not. If you need a separate behavior (something that doesn't retain the previous value), then you should use a component that doesn't retain the previous value -- instead of hacking around it ("remembering" that it had emitted and then forgetting to emit, etc.)
You can add event-emitter
library:
implementation 'com.github.Zhuinden:live-event:1.1.0'
from Jitpack: maven { url "https://jitpack.io" }
Then you can do
private val eventEmitter = EventEmitter<WordController.Events>()
val controllerEvents: EventSource<WordController.Events> = eventEmitter
and
controllerEvents.observe(viewLifecycleOwner) { event: WordController.Events ->
when (event) {
is WordController.Events.NewWordAdded -> showToast("Added ${event.word}")
}.safe()
}
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.
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.
You can use EventLiveData described in this article. It is LiveData extension just like SingleLiveData but supports multiple observers. Also allows custom lifecycle limitation when observers should receive events. For example if you don't want to receive events when your fragments is in background.
EventLiveData holds internal observer that it observes forever, overrides observe method saving observers into internal map bypassing native LiveData event dispatching mechanics.
public class EventLiveData<T> extends LiveData<T> {
private final HashMap<Observer<? super T>, EventObserverWrapper> observers= new HashMap<>();
private final Observer<T> internalObserver;
int mActiveCount = 0;
public EventLiveData() {
this.internalObserver = (new Observer<T>() {
@Override
public void onChanged(T t) {
Iterator<Map.Entry<Observer<? super T>, EventObserverWrapper>> iterator = EventLiveData.this.observers.entrySet().iterator();
while (iterator.hasNext()){
EventObserverWrapper wrapper= iterator.next().getValue();
if(wrapper.shouldBeActive())
wrapper.getObserver().onChanged(t);
}
}
});
}
private void internalObserve(){
super.observeForever(this.internalObserver);
}
@MainThread
@Override
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer observer) {
observe(owner, observer,STARTED,null);
}
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer observer, @NonNull Lifecycle.State minimumStateForSendingEvent) {
observe(owner, observer,minimumStateForSendingEvent,null);
}
@MainThread
public void observeInOnStart(@NonNull LifecycleOwner owner, @NonNull Observer observer) {
observe(owner, observer,STARTED, Lifecycle.Event.ON_STOP);
}
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer observer, @NonNull Lifecycle.State minimumStateForSendingEvent, Lifecycle.Event removeObserverEvent) {
assertMainThread("observe");
assertNotNull(owner, "owner");
assertNotNull(observer, "observer");
assertNotNull(owner, "minimumStateForSendingEvent");
assertDestroyedState(minimumStateForSendingEvent);
assertMaximumEvent(removeObserverEvent);
if(minimumStateForSendingEvent==DESTROYED){
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
StackTraceElement caller = stackTraceElements[3];
String className = caller.getClassName();
String methodName = caller.getMethodName();
IllegalArgumentException exception =
new IllegalArgumentException("State can not be equal to DESTROYED! : " +
"method " + className + "." + methodName +
", parameter " + minimumStateForSendingEvent);
throw sanitizeStackTrace(exception);
}
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
return;
}
EventLifecycleBoundEventObserver wrapper = new EventLifecycleBoundEventObserver(owner, observer);
wrapper.setMinimumStateForSendingEvent(minimumStateForSendingEvent);
wrapper.setMaximumEventForRemovingEvent(removeObserverEvent);
EventObserverWrapper existing = wrapper;
if(!observers.containsKey(observer))existing = observers.put(observer, wrapper);
if (existing != null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
owner.getLifecycle().addObserver(wrapper);
if (!super.hasObservers()) {
internalObserve();
}
}
@MainThread
@Override
public void observeForever(@NonNull Observer observer) {
assertMainThread("observeForever");
assertNotNull(observer, "observer");
EventAlwaysActiveEventObserver wrapper = new EventAlwaysActiveEventObserver(observer);
EventObserverWrapper existing = wrapper;
if(!observers.containsKey(observer))existing = observers.put(observer, wrapper);
if (existing != null && existing instanceof EventLiveData.EventLifecycleBoundEventObserver) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
if (!super.hasObservers()) {
internalObserve();
}
wrapper.activeStateChanged(true);
}
/**
{@inheritDoc}
*/
@Override
public void removeObservers(@NonNull LifecycleOwner owner) {
assertMainThread("removeObservers");
assertNotNull(owner, "owner");
Iterator<Map.Entry<Observer<? super T>, EventObserverWrapper>> iterator = EventLiveData.this.observers.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry<Observer<? super T>, EventObserverWrapper> entry=iterator.next();
if(entry.getValue() instanceof EventLiveData.EventLifecycleBoundEventObserver){
EventLifecycleBoundEventObserver eventLifecycleBoundObserver =(EventLifecycleBoundEventObserver) entry.getValue();
if(eventLifecycleBoundObserver.isAttachedTo(owner))this.observers.remove(entry.getKey());
}
}
}
@Override
public void removeObserver(@NonNull Observer observer) {
assertMainThread("removeObserver");
assertNotNull(observer, "observer");
this.observers.remove(observer);
}
final protected void onActive() {}
protected void onActiveEvent() {}
protected void onInactive() {
}
@SuppressWarnings("WeakerAccess")
public boolean hasObservers() {
return observers.size() > 0;
}
@SuppressWarnings("WeakerAccess")
public boolean hasActiveObservers() {
return mActiveCount > 0;
}
class EventLifecycleBoundEventObserver extends EventObserverWrapper implements LifecycleObserver {
@NonNull
private final LifecycleOwner mOwner;
private Lifecycle.State MINIMUM_STATE_FOR_SENDING_EVENT= STARTED;
private Lifecycle.Event MAXIMUM_EVENT_FOR_REMOVING_EVENT= null;
EventLifecycleBoundEventObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
super(observer);
mOwner = owner;
}
public Lifecycle.State getMinimumStateForSendingEvent() {
return MINIMUM_STATE_FOR_SENDING_EVENT;
}
public Lifecycle.Event getMaximumStateForRemovingEvent() {
return MAXIMUM_EVENT_FOR_REMOVING_EVENT;
}
public void setMaximumEventForRemovingEvent(Lifecycle.Event MAXIMUM_EVENT_FOR_REMOVING_EVENT) {
this.MAXIMUM_EVENT_FOR_REMOVING_EVENT = MAXIMUM_EVENT_FOR_REMOVING_EVENT;
}
public void setMinimumStateForSendingEvent(Lifecycle.State MINIMUM_STATE_FOR_SENDING_EVENT) {
this.MINIMUM_STATE_FOR_SENDING_EVENT = MINIMUM_STATE_FOR_SENDING_EVENT;
}
@Override
boolean shouldBeActive() {
Lifecycle.State state=mOwner.getLifecycle().getCurrentState();
return state.isAtLeast(MINIMUM_STATE_FOR_SENDING_EVENT);
}
@OnLifecycleEvent(Lifecycle.Event.ON_ANY)
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
if (mOwner.getLifecycle().getCurrentState() == DESTROYED||(MAXIMUM_EVENT_FOR_REMOVING_EVENT!=null&&MAXIMUM_EVENT_FOR_REMOVING_EVENT==event)) {
removeObserver(mObserver);
return;
}
activeStateChanged(shouldBeActive());
}
@Override
boolean isAttachedTo(LifecycleOwner owner) {
return mOwner == owner;
}
@Override
void detachObserver() {
mOwner.getLifecycle().removeObserver(this);
}
}
private abstract class EventObserverWrapper {
protected final Observer<? super T> mObserver;
boolean mActive;
EventObserverWrapper(Observer<? super T> observer) {
mObserver = observer;
}
abstract boolean shouldBeActive();
boolean isAttachedTo(LifecycleOwner owner) {
return false;
}
void detachObserver() {
}
public Observer<? super T> getObserver() {
return mObserver;
}
void activeStateChanged(boolean newActive) {
if (newActive == mActive) {
return;
}
// immediately set active state, so we'd never dispatch anything to inactive
// owner
mActive = newActive;
boolean wasInactive = EventLiveData.this.mActiveCount == 0;
EventLiveData.this.mActiveCount += mActive ? 1 : -1;
if (wasInactive && mActive) {
onActiveEvent();
}
if (EventLiveData.this.mActiveCount == 0 && !mActive) {
onInactive();
}
}
}
private class EventAlwaysActiveEventObserver extends EventObserverWrapper {
EventAlwaysActiveEventObserver(Observer<? super T> observer) {
super(observer);
}
@Override
boolean shouldBeActive() {
return true;
}
}
private void assertDestroyedState(@NonNull Lifecycle.State minimumStateForSendingEvent){
if(minimumStateForSendingEvent==DESTROYED){
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
StackTraceElement caller = stackTraceElements[3];
String className = caller.getClassName();
String methodName = caller.getMethodName();
IllegalArgumentException exception =new IllegalArgumentException("State can not be equal to "+ minimumStateForSendingEvent +"method " + className + "." + methodName +", parameter minimumStateForSendingEvent");
throw sanitizeStackTrace(exception);}
}
private void assertMaximumEvent(@NonNull Lifecycle.Event maximumEventForRemovingEvent){
if(maximumEventForRemovingEvent== Lifecycle.Event.ON_START||maximumEventForRemovingEvent== Lifecycle.Event.ON_CREATE
||maximumEventForRemovingEvent== Lifecycle.Event.ON_RESUME){
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
StackTraceElement caller = stackTraceElements[3];
String className = caller.getClassName();
String methodName = caller.getMethodName();
IllegalArgumentException exception = new IllegalArgumentException("State can not be equal to "+maximumEventForRemovingEvent + "method " + className + "." + methodName +", parameter maximumEventForRemovingEvent" );
throw sanitizeStackTrace(exception);
}
}
private void assertMainThread(String methodName) {
boolean isUiThread = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? Looper.getMainLooper().isCurrentThread() : Thread.currentThread() == Looper.getMainLooper().getThread();
if (!isUiThread) {throw new IllegalStateException("Cannot invoke " + methodName + " on a background"+ " thread"); }
}
private void assertNotNull(Object value, String paramName) {
if (value == null) {throwParameterIsNullException(paramName); } }
private void throwParameterIsNullException(String paramName) {
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
StackTraceElement caller = stackTraceElements[3];
String className = caller.getClassName();
String methodName = caller.getMethodName();
IllegalArgumentException exception =
new IllegalArgumentException("Parameter specified as non-null is null: " +
"method " + className + "." + methodName +
", parameter " + paramName);
throw sanitizeStackTrace(exception);
}
private <T extends Throwable> T sanitizeStackTrace(T throwable) { return sanitizeStackTrace(throwable, this.getClass().getName());}
<T extends Throwable> T sanitizeStackTrace(T throwable, String classNameToDrop) {
StackTraceElement[] stackTrace = throwable.getStackTrace();
int size = stackTrace.length;
int lastIntrinsic = -1;
for (int i = 0; i < size; i++) {
if (classNameToDrop.equals(stackTrace[i].getClassName())) {lastIntrinsic = i; } }
StackTraceElement[] newStackTrace = Arrays.copyOfRange(stackTrace, lastIntrinsic + 1, size);
throwable.setStackTrace(newStackTrace);
return throwable;
}
}