56

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.

The problem related to communication between ViewModel and fragment, Google gave us LiveData to update the view with data, but this type of communication not suitable when we need update the view only once with single event, also we cannot hold view's reference in ViewModel and call some methods because it will create memory leak.

I found something similar SingleLiveEvent - but it work only for 1 observer and not for multiple observers.

--- Update ----

As @EpicPandaForce said "There is no reason to use LiveData as something that it is not", probably the intent of the question was Communication between view and ViewModel in MVVM with LiveData

Pavel Poley
  • 5,307
  • 4
  • 35
  • 66

16 Answers16

19

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<>();

 public 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();
          }
        });
Reejesh
  • 1,745
  • 2
  • 6
  • 15
Jurij Pitulja
  • 5,546
  • 4
  • 19
  • 25
9

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.

d4vidi
  • 2,407
  • 26
  • 27
  • You are never removing the observers. The `removeObserver` method is called from supper supplying your private `ObserverWrapper` type. You need to unwrap that. – Olsi Saqe Apr 01 '22 at 12:05
9

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
        if (delegate != (other as FreshObserver<*>).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()
}
Vlad
  • 7,997
  • 3
  • 56
  • 43
kxfeng
  • 306
  • 3
  • 6
6

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.

  1. Add a flag to the ViewModel.

    private boolean isFirstTime = true;
    
    public boolean isFirstTime() { return isFirstTime; }
    
    public boolean onObserverAdded() { isFirstTime = false; }`
    
  2. 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
    }
    
  3. Finally call onObserverAdded() after observer is added.

Bertram Gilfoyle
  • 9,899
  • 6
  • 42
  • 67
5

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....

emandt
  • 2,547
  • 2
  • 16
  • 20
4

According to answer of jurij-pitulja.

if we are using kotlin coroutines the solution look likes this.

class Event<T>(private val content: T) {

    var isHandled = false
    private set

    fun getContentIfNotHandled(): T? {
        return takeIf { !isHandled }?.let {
            isHandled = true
            content
        }
    }
}

Inside of view model class replacing Flow.asLiveData() into emit new Event

val authResult: LiveData<Event<Result<AuthResponse>>> = _emailLiveData.switchMap { email ->
    liveData{
        repository.authRequest(email).collect{
            emit(Event(it))
        }
    }
}

Implementing observer method inside of fragment

viewModel.authResult.observe(viewLifecycleOwner){
            it.getContentIfNotHandled()?.run {
                onAuthRequestComplete(this)
            }
        }
Vahe Gharibyan
  • 5,277
  • 4
  • 36
  • 47
3

I created a LiveData object FreshLiveData, which emits the onChange to the observer only after there is a call to setValue or postValue.

FreshLiveData.kt

/**
 * A lifecycle-aware observable that emits only new data after subscription. Any data that has
 * already been set, before the observable has subscribed, will be ignored.
 *
 * This avoids a common problem with events: on configuration change (like rotation, font change) an
 * update can be emitted if the observer is active. This LiveData only calls the observable if
 * there's an explicit call to setValue() or postValue().
 *
 * All observers will be notified of change(s).
 */
class FreshLiveData<T> : MutableLiveData<T>() {

    private val observers = mutableMapOf<LifecycleOwner, FreshLiveDataObserver>()

    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        @Suppress("UNCHECKED_CAST")
        observer as Observer<T>
        observers[owner].apply {
            if (this == null) {
                observers[owner] = FreshLiveDataObserver(observer).apply {
                    super.observe(owner, this)
                }
            } else {
                add(observer)
            }
        }
    }

    override fun observeForever(observer: Observer<in T>) {
        @Suppress("UNCHECKED_CAST")
        observer as Observer<T>
        observers[ProcessLifecycleOwner.get()].apply {
            if (this == null) {
                observers[ProcessLifecycleOwner.get()] = FreshLiveDataObserver(observer).apply {
                    super.observeForever(this)
                }
            } else {
                add(observer)
            }
        }
    }

    override fun removeObservers(owner: LifecycleOwner) {
        observers.remove(owner)
        super.removeObservers(owner)
    }

    override fun removeObserver(observer: Observer<in T>) {
        @Suppress("UNCHECKED_CAST")
        observers.forEach { it.value.remove(observer as Observer<T>) }
        super.removeObserver(observer)
    }

    @MainThread
    override fun setValue(t: T?) {
        observers.forEach { it.value.setPending() }
        super.setValue(t)
    }

    override fun postValue(value: T) {
        observers.forEach { it.value.setPending() }
        super.postValue(value)
    }

    inner class FreshLiveDataObserver(observer: Observer<T>) : Observer<T> {
        private val observers = mutableSetOf<Observer<T>>()
        private val pending = AtomicBoolean(false)

        init {
            observers.add(observer)
        }

        fun add(observer: Observer<T>) = observers.add(observer)
        fun remove(observer: Observer<T>) = observers.remove(observer)
        fun setPending() = pending.set(true)

        override fun onChanged(t: T) {
            if (pending.compareAndSet(true, false)) {
                observers.forEach { observer ->
                    observer.onChanged(t)
                }
            }
        }

    }
}

and here is an extension for transforming an existing LiveData to a FreshLiveData.

LiveDataExtensions.kt

@MainThread
fun <T> LiveData<T>.toFreshLiveData(): LiveData<T> {
    val freshLiveData = FreshLiveData<T>()
    val output = MediatorLiveData<T>()
    // push any onChange from the LiveData to the FreshLiveData
    output.addSource(this) { liveDataValue -> freshLiveData.value = liveDataValue }
    // then push any onChange from the FreshLiveData out
    output.addSource(freshLiveData) { freshLiveDataValue -> output.value = freshLiveDataValue }
    return output
}

Usage:

val liveData = MutableLiveData<Boolean>()
liveData.value = false
liveData.toFreshLiveData().observeForever {
    // won't get called with `it = false` because the observe was setup after setting that livedata value
    // will get called with `it = true` because the observer was setup before setting that livedata value
}
liveData.value = false

val freshLiveData = FreshLiveData<Boolean>()
freshLiveData.value = false
freshLiveData.observeForever {
    // won't get called with `it = false` because the observe was setup after setting that livedata value
    // will get called with `it = true` because the observer was setup before setting that livedata value
}
freshLiveData.value = true
Jason Grife
  • 1,197
  • 11
  • 14
3

Assuming you're observing live data from an activity, fragment or other view with a lifecycle, a simple approach would be to ignore the initial 'last value' by doing nothing if the observer is notified when the activity or fragment isn't visible/resumed yet:

MainActivity.kt

myviewmodel.myLiveData.observe(this) {
    if (lifecycle.currentState != Lifecycle.State.RESUMED) {
        return@observe
    }
}

or, if observing a VM from a Fragment:

myviewmodel.myLiveData.observe(viewLifecycleOwner) {
    if (viewLifecycleOwner.lifecycle.currentState != Lifecycle.State.RESUMED) {
        return@observe
    }
}

Any component with a lifecycle that observes live data can perform this check each time they receive an update. If the component isn't currently visible on screen, we toss the update aside and do nothing. As soon as we observe, the component will still receive its last value data from the VM, but will not yet be visible/resumed, so we just ignore it.

rmirabelle
  • 6,268
  • 7
  • 45
  • 42
  • 1
    Thank you so much.. I wasted a lot of time regarding this issue. Finally got some solution. Kudos !! – Nayan Apr 07 '23 at 14:43
2

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);
    }
}
BeingHuman
  • 61
  • 4
  • Would not work since observers which are in background would not get updated values after coming to foreground. – Egor Neliuba Sep 18 '19 at 15:52
  • @EgorNeliuba Would you mind explaining more why this don't work? Isn't preventing the observer getting trigger after coming back to foreground what we are trying to solve? – BabyishTank Sep 15 '21 at 21:41
1

You can use EventLiveData described in this article. It will solve your issue , I used it in 2 production projects. 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.

You can copy/paste whole class or import lib which is more convenient way

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;
}

}

1

More simple solution would be to use EventLiveData lib :

implementation 'com.rugovit.eventlivedata:eventlivedata:1.0'

MutableEventLiveData<String>  eventLiveData =new MutableEventLiveData<>(); 
viewModel.event.observe(this, Observer {
    // ...
})
 

You use it just like regular live data. It is extension of livedata and supports every feature of livedata. Unlike other solutions this supports multiple observers.

Github link: https://github.com/rugovit/EventLiveData

1

I have written a small utility class that quickly enables me to block first run of any block of code like this:

class SkipFirstRun {
    var canRun = false
    fun run(codeToRunButNotFirstTime: () -> Unit) {
        if (canRun) codeToRunButNotFirstTime()
        canRun = true
    }
}

In my Fragment I then just declare an instance var of it:

class TrainingDetailCardInfoFragment 
    override val viewModel: TrainingViewModel by viewModels 
    private var skipInitialEndOfTraining = SkipFirstRun()
...

and then use it like so

    viewModel.runningTask.observe(viewLifecycleOwner) { task ->
            skipInitialEndOfTraining.run {
                Log.debug("Playing stopped!")
                sound.play(requireContext(), R.raw.endoftraining)
                playBarViewModel.stop()
            }
    }
HixField
  • 3,538
  • 1
  • 28
  • 54
0

Just ignore data before android.arch.lifecycle.LiveData#observe function called.

class IgnoreHistoryLiveData<T> : MutableLiveData<T>() {
    private val unactivedObservers = LinkedBlockingQueue<WrapperObserver<T>>()
    override fun observe(owner: LifecycleOwner, observer: Observer<T>) {
        val wo = WrapperObserver<T>(observer)
        unactivedObservers.add(wo)
        super.observe(owner, wo)
    }


    override fun setValue(value: T) {
        while (unactivedObservers.isNotEmpty()) {
            unactivedObservers.poll()?.actived = true
        }
        super.setValue(value)
    }
}

private class WrapperObserver<T>(private val origin: Observer<T>) : Observer<T> {
    var actived = false
    override fun onChanged(t: T?) {
        if (actived) {
            origin.onChanged(t)
        }
    }
}
Fantasy_RQG
  • 143
  • 1
  • 13
0

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.)

Although there was no other correct solution available, so I ended up having to write one myself, so as there is no alternative that I would recommend, I'll have to recommend the one I wrote for this specific purpose.

Anyways, you can add live-event library:

implementation 'com.github.Zhuinden:live-event:1.2.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()
}
EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • 18
    Using this library is BEYOND the purpose/scope of main question. The Author asked a solution for LiveData and not a generic workaround/solution to reach his achievement. – emandt Jan 14 '20 at 16:02
  • 4
    LiveData was not designed and is not suitable for this. Everything else is a trick. – EpicPandaForce Jan 14 '20 at 17:16
  • How is it possible that it is not designed for such a simple use case? For example: ViewModel needs to notify View that error occurred and after that View shows SnackBar. Then start observing again that observer (activity restarted) and you get error message. Do I still need some workarounds or hacks for this use case? Simplest workaround for me would be RxJava. – Nikola Samardzija Feb 04 '20 at 07:25
  • 3
    @Nikola LiveData works like a BehaviorRelay, it will always emit the last emitted value. If you need a PublishRelay, then LiveData will not be a suitable solution. The "official Google sample" uses `EventObserver` + `LiveData>` but in reality that's just `SingleLiveData` with more code, which is already like `BehaviorRelay.skip(1)` which is just awkward. – EpicPandaForce Feb 04 '20 at 08:58
  • 1
    This problem cannot be solved with LiveData without bringing in some hack or fighting against the framework. The library introduced by @EpicPandaForce is simple, well tested, and most importantly, solved the problem at hand for me. – Phileo99 Jul 29 '21 at 20:17
0

I come up against this problem repeatedly and it's appears to be an issue with Flow and with the new recommended guidance on consuming single UI events. The reason it is hard for me to get my head around is that there's subtle differences in how you need to consume events.

The famous EventWrapper is handling a use case of:

start observing -> event occurs -> re-establish observation and make sure we don't get that event again

However what if the act of observing in the first place is also firing the latest data? It seems that none of the official guidance addresses that if you post something to LiveData by design it will propagate on first observation if it has not been observed yet, even if you are using Flow or the EventWrapper.

There's:

  • Events you only want to consume once (SingleLiveEvent)
  • Events you want multiple observers to only consume once (EventWrapper, or the current architectural guidance using StateFlow + lists + IDs with a load of crazy boiler plate)
  • And Events you want to consume only after you start observing to one or more observers (this question)

This is a particularly a problem when a view model is shared between two fragments. i.e. Fragment A posts a value to live data, then a few clicks or network responses later Fragment B is on screen and starts observing the same shared live data and you want it to throw out that latest result and only use future ones. In this case you are using LiveData for something it was not designed to do, and need to look at the conditions as to why Fragment B shouldn't care about the latest result and use logic in the observation to filter it out.

Daniel Wilson
  • 18,838
  • 12
  • 85
  • 135
0

Check this: https://github.com/san-sk/android-kotlin-extensions/blob/main/LiveDataUtils.kt

/**
Required Lib
Kotlin reflection - https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-reflect
*/

import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer

/**
 * Extension to access the private functions of a class
 */
inline fun <reified T> T.callPrivateFunc(name: String, vararg args: Any?): Any? {
    val classArray: Array<Class<*>> = args.map { it!!::class.java }.toTypedArray()
    return T::class.java.getDeclaredMethod(name, *classArray)
        .apply { isAccessible = true }
        .invoke(this, *args)
}

/**
 * Get LiveData private function [LiveData.getVersion]
 *
 * Uses reflection concept
 */
fun LiveData<*>.version(): Int {
    return this.callPrivateFunc("getVersion") as Int
}

/**
 * Use this to get the latest value from live data.
 * This will skip the initial value and emit the latest one.
 *
 * Usage: to get values only after hitting the api
 */
fun <T> LiveData<T>.observeLatest(owner: LifecycleOwner, observer: Observer<in T>) {
    val sinceVersion = this.version()
    this.observe(owner, LatestObserver<T>(observer, this, sinceVersion))
}

fun <T> LiveData<T>.observeForeverLatest(
    observer: Observer<in T>,
    skipPendingValue: Boolean = true
) {
    val sinceVersion = this.version()
    this.observeForever(LatestObserver<T>(observer, this, sinceVersion))
}

// Removes the observer which has been previously observed by [observeFreshly] or [observeForeverFreshly].
fun <T> LiveData<T>.removeObserverLatest(observer: Observer<in T>) {
    this.removeObserver(LatestObserver<T>(observer, this, 0))
}

class LatestObserver<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
        if (delegate != (other as LatestObserver<*>).delegate) return false
        return true
    }

    override fun hashCode(): Int {
        return delegate.hashCode()
    }
}
san
  • 1