Reputation: 171
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.
Upvotes: 7
Views: 4493
Reputation: 385
@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
}
}
Upvotes: -1
Reputation: 644
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)
})
}
...
}
Upvotes: 0
Reputation: 692
I faced the same problem and came up with this library to solve it https://github.com/ueen/LiveEvent Hope this helps, enjoy!
Upvotes: 0
Reputation: 169
Problem with accepted answer is that you can only have one observer. This article describes solution with multiple observers.
Upvotes: 1
Reputation: 769
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.
Upvotes: 0
Reputation: 1891
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>>()
Upvotes: 5