How to stop LiveData event being triggered more than Once
Asked Answered
E

6

7

I am using MutableLiveData within my application for event based communication. I have single activity two fragments architecture.

With the help of ViewModel, I'm consuming the LiveData events in Fragment-1. But, when I replace this Fragment-1 with Fragment-2 using Menu bar and finally come back to Fragment-1, old values of LiveData are captured again.

How to avoid this problem? Any help/suggestions are highly appreciated! Thank you.

Erinaceous answered 14/2, 2019 at 10:59 Comment(0)
A
5

You can use Event to wrap LiveData values to handle consuming its values as in the following article: https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150

Event class would be like:

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

    var hasBeenHandled = false
        private set // Allow external read but not write

    /**
     * Returns the content and prevents its use again.
     */
    fun getContentIfNotHandled(): T? {
        return if (hasBeenHandled) {
            null
        } else {
            hasBeenHandled = true
            content
        }
    }

    /**
     * Returns the content, even if it's already been handled.
     */
    fun peekContent(): T = content
}

Let us say that your LiveData value is a String then the LiveData of single event would be like:

val navigateToDetails = MutableLiveData<Event<String>>()

Ankerite answered 14/2, 2019 at 11:3 Comment(2)
Works!! Thanks:)Erinaceous
note for new comers in 2024 , the referenced medium article states: "2021 Update: This guidance is deprecated in favor of the official guidelines." official guidlines: developer.android.com/topic/architecture/ui-layer/eventsGoering
S
1

Problem with accepted answer is that you can only have one observer. This article describes solution with multiple observers.

Sultana answered 9/1, 2020 at 3:36 Comment(0)
H
0

Wherever you're observing the liveData, in onChanged method remove the observers by calling myLiveDataObject.removeObservers(this); This will remove the observer after first-time data is observed.

Herzel answered 14/2, 2019 at 11:26 Comment(0)
H
0

I faced the same problem and came up with this library to solve it https://github.com/ueen/LiveEvent Hope this helps, enjoy!

Hoisch answered 18/1, 2020 at 0:14 Comment(0)
X
0

Simple, clean, reusable:

class Event<T>(val payload: T, var broadcasted: Boolean = false)

class MutableEventLiveData<T>: MutableLiveData<Event<T>>() {
    fun postEvent(value: T) {
        super.postValue(Event(value))
    }
}

typealias EventLiveData<T> = LiveData<Event<T>>

class EventObserver<T>(private val broadcastCallback: (t: T)->Unit): Observer<Event<T>> {

    override fun onChanged(e: Event<T>) {
        if (!e.broadcasted) {
            broadcastCallback(e.payload)
            e.broadcasted = true
        }
    }
}

Sample usage:

class YourViewModel : ViewModel() {
    private val _errorEvent = MutableEventLiveData<String>()
    val errorEvent: EventLiveData<String>
        get() = _errorEvent

    fun fireErrorEvent(errorMessage: String) {
        _errorEvent.postEvent(errorMessage)
    }
    ...
}

class YourActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        //Note!!! EventObserver handles events not Observer
        viewModel.errorEvent.observe(this, EventObserver { 
            errorMessage -> showErrorMessage(errorMessage)
        })
    }
    ...
}
Xerophagy answered 11/11, 2021 at 12:30 Comment(0)
B
-1

@Ningan, my idea is to provide a base class of one-time events to be inherited into client code, and wrap each observer received from the client code into an internal one that operates on the base class.

OnceEventLiveData.java (also available as Github Gist):

public class OnceEventLiveData<T extends OnceEventLiveData.OnceEvent> extends MutableLiveData<T> {
    @Override
    public void observe(@NonNull final LifecycleOwner owner,
                        @NonNull final Observer<? super T> observer) {
        super.observe(owner, new OnceEventObserver<>(observer));
    }

    @Override
    public void observeForever(@NonNull final Observer<? super T> observer) {
        super.observeForever(new OnceEventObserver<>(observer));
    }

    @Override
    public void removeObserver(@NonNull final Observer<? super T> observer) {
        super.removeObserver(new OnceEventObserver<>(observer));
    }

    private static class OnceEventObserver<T extends OnceEvent> implements Observer<T> {
        @NonNull
        private final Observer<OnceEvent> mObserver;

        OnceEventObserver(@NonNull final Observer<? super T> observer) {
            //noinspection unchecked
            mObserver = (Observer<OnceEvent>) observer;
        }

        @Override
        public void onChanged(@NonNull OnceEvent event) {
            if (!event.mIsConsumed.get()) {
                mObserver.onChanged(event);
                event.mIsConsumed.getAndSet(true);
            }
        }

        @Override
        public boolean equals(@Nullable final Object other) {
            if (this == other) return true;
            if (other == null || getClass() != other.getClass()) return false;

            OnceEventObserver<?> that = (OnceEventObserver<?>) other;

            return mObserver.equals(that.mObserver);
        }

        @Override
        public int hashCode() {
            return mObserver.hashCode();
        }
    }

    /**
     * Declares a base class for any consumer data that be intended for use into this LiveData
     * implementation.
     */
    public abstract static class OnceEvent {
        @NonNull
        private final AtomicBoolean mIsConsumed = new AtomicBoolean(false);
    }
}

Abstract OnceEvent class encapsulates it own "is consumed" status. It makes necessary to inherit the base class and create separate classes for each type of event.

On the other hand, the View and ViewModel do not change compared to use regular LiveData, you can add and remove any needed observers; all one-time processing logic is well-encapsulated into nested classes. It allows you to use OnceEventLiveData as any others LiveData, and to be focused only on the meaning of your custom event.

Usage in ViewModel :

class EventViewModel extends ViewModel {


    public static class CustomEvent extends OnceEventLiveData.OnceEvent {
        // any needed event's payload here
    }


    @NonNull
    private final OnceEventLiveData<CustomEvent> mEvent = new OnceEventLiveData<>();

    @NonNull
    public LiveData<CustomEvent> getEvent() {
        return mEvent;
    }

    public void doSomething() {
        // business logic that beget the event
        mEvent.setValue(new CustomEvent());
    }

}

Usage in Activity/Fragment :

class EventActivity extends AppCompatActivity implements Observer<CustomEvent> {


    @Override
    protected void onCreate(@Nullable final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        EventViewModel eventViewModel = new ViewModelProvider(this).get(EventViewModel.class);
        eventViewModel.getEvent().observe(this, this);
    }

    @Override
    public void onChanged(@NonNull CustomEvent event) {
        // eevent handling logic here
    }


}
Brause answered 29/1 at 14:22 Comment(1)
Dear colleagues, thank you for the clarifications. I'm going to edit my answer to provide more detail about solution.Brause

© 2022 - 2024 — McMap. All rights reserved.