Is it possible to prevent LiveData receive the last value when start observing?
I am considering to use LiveData as events.
For example eve
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 extends LiveData {
private final HashMap, EventObserverWrapper> observers= new HashMap<>();
private final Observer internalObserver;
int mActiveCount = 0;
public EventLiveData() {
this.internalObserver = (new Observer() {
@Override
public void onChanged(T t) {
Iterator, 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, EventObserverWrapper>> iterator = EventLiveData.this.observers.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry, 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 sanitizeStackTrace(T throwable) { return sanitizeStackTrace(throwable, this.getClass().getName());}
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;
}
}