77

According to LiveData documentation:

The LiveData class provides the following advantages:

...

Always up to date data: If a Lifecycle starts again (like an activity going back to started state from the back stack) it receives the latest location data (if it didn’t already).

But sometimes I don't need this feature.

For example, I have following LiveData in ViewModel and Observer in Activity:

//LiveData
val showDialogLiveData = MutableLiveData<String>()

//Activity
viewModel.showMessageLiveData.observe(this, android.arch.lifecycle.Observer { message ->
        AlertDialog.Builder(this)
                .setMessage(message)
                .setPositiveButton("OK") { _, _ -> }
                .show()
    })

Now after every rotation old dialog will appear.

Is there a way to clear stored value after it's handled or is it wrong usage of LiveData at all?

Community
  • 1
  • 1
Kamer358
  • 2,430
  • 2
  • 16
  • 17
  • 2
    this is related to: https://stackoverflow.com/questions/44146081/show-dialog-from-viewmodel-in-android-mvvm-architecture – Alex Radzishevsky Jun 16 '17 at 08:19
  • Does it related to the live data issue? The activity will be recreated every time you rotated no matter you used LiveData or not. The issue will continue even u remove it. – Long Ranger Jun 20 '17 at 15:05
  • 1
    @LongRanger it can be solved by deleting message cached in LiveData after dialog shown, so new activity won't receive it. Same principle used on Moxy's [OneExecutionStateStrategy](https://github.com/Arello-Mobile/Moxy/wiki/View-commands-state-strategy#existing-strategies) – Kamer358 Jun 21 '17 at 05:25

11 Answers11

75

Update

There are actually a few ways to resolve this issue. They are summarized nicely in the article LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case). This is written by a fellow Googler who works with the Architecture Components team.

TL;DR A more robust approach is to use an Event wrapper class, which you can see an example of at the bottom of the article.

This pattern has made it's way into numerous Android samples, for example:

Why is an Event wrapper preferred over SingleLiveEvent?

One issue with SingleLiveEvent is that if there are multiple observers to a SingleLiveEvent, only one of them will be notified when that data has changed - this can introduce subtle bugs and is hard to work around.

Using an Event wrapper class, all of your observers will be notified as normal. You can then choose to either explicitly "handle" the content (content is only "handled" once) or peek at the content, which always returns whatever the latest "content" was. In the dialog example, this means you can always see what the last message was with peek, but ensure that for every new message, the dialog only is triggered once, using getContentIfNotHandled.

Old Response

Alex's response in the comments is I think exactly what you're looking for. There's sample code for a class called SingleLiveEvent. The purpose of this class is described as:

A lifecycle-aware observable that sends only new updates after subscription, used for events like navigation and Snackbar messages.

This avoids a common problem with events: on configuration change (like rotation) 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 call().

Lyla
  • 2,767
  • 1
  • 21
  • 23
9

I`m not sure if it will work in your case, but in my case (increasing/decreasing items amount in Room by click on views) removing Observer and checking if there is active observers let me do the job:

LiveData<MenuItem> menuitem = mViewModel.getMenuItemById(menuid);
menuitem.observe(this, (MenuItem menuItemRoom) ->{
                menuitem.removeObservers(this);
                if(menuitem.hasObservers())return;

                // Do your single job here

                });
});  

UPDATE 20/03/2019:

Now i prefer 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<>();

 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();
          }
        });
Jurij Pitulja
  • 5,546
  • 4
  • 19
  • 25
8

If you need simple solution, try this one:

class SingleLiveData<T> : MutableLiveData<T?>() {

    override fun observe(owner: LifecycleOwner, observer: Observer<in T?>) {
        super.observe(owner, Observer { t ->
            if (t != null) {
                observer.onChanged(t)
                postValue(null)
            }
        })
    }
}

Use it like a regular MutableLiveData

Nynuzoud
  • 101
  • 1
  • 3
6

In my case SingleLiveEvent doesn't help. I use this code:

private MutableLiveData<Boolean> someLiveData;
private final Observer<Boolean> someObserver = new Observer<Boolean>() {
    @Override
    public void onChanged(@Nullable Boolean aBoolean) {
        if (aBoolean != null) {
            // doing work
            ...

            // reset LiveData value  
            someLiveData.postValue(null);
        }
    }
};
Final12345
  • 71
  • 1
  • 3
  • Obviously just guessing, but it may not be helping in your case because you're using `postValue` and the example SingleLiveEvent implementation doesn't cater for that. Easy fix though, add - ``` override fun postValue(value: T) { pending.set(true) super.postValue(value) } ``` – Max Stewart Feb 16 '19 at 16:53
3

I solved it like that. Live data will clear itself when there is no observer

class SelfCleaningLiveData<T> : MutableLiveData<T>(){
    override fun onInactive() {
       super.onInactive()
       value = null
    }

} 
oğuzhan
  • 41
  • 3
2

You need to use SingleLiveEvent for this case

class SingleLiveEvent<T> : MutableLiveData<T>() {

    private val pending = AtomicBoolean(false)

    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<T>) {

        if (hasActiveObservers()) {
            Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
        }

        // Observe the internal MutableLiveData
        super.observe(owner, Observer<T> { t ->
            if (pending.compareAndSet(true, false)) {
                observer.onChanged(t)
            }
        })
    }

    @MainThread
    override fun setValue(t: T?) {
        pending.set(true)
        super.setValue(t)
    }

    /**
     * Used for cases where T is Void, to make calls cleaner.
     */
    @MainThread
    fun call() {
        value = null
    }

    companion object {
        private const val TAG = "SingleLiveEvent"
    }
}

And inside you viewmodel class create object like:

 val snackbarMessage = SingleLiveEvent<Int>()
Ankit Bisht
  • 1,209
  • 12
  • 14
0

The best solution I found is live event library which works perfectly if you have multiple observers:

class LiveEventViewModel : ViewModel() {
    private val clickedState = LiveEvent<String>()
    val state: LiveData<String> = clickedState

    fun clicked() {
        clickedState.value = ...
    }
}
Morteza Rastgoo
  • 6,772
  • 7
  • 40
  • 61
  • 1
    The one thing to be careful about with this library is that it can't emit the latest value to new observers, as standard LiveData does. Otherwise I love it. – Carson Holzheimer Sep 09 '19 at 06:35
0

Might be an ugly hack but... Note: it requires RxJava

menuRepository
            .getMenuTypeAndMenuEntity(menuId)
            .flatMap { Single.fromCallable { menuTypeAndId.postValue(Pair(it.first, menuId)) } }
            .flatMap { Single.timer(200, TimeUnit.MILLISECONDS) }
            .subscribe(
                { menuTypeAndId.postValue(null) },
                { Log.d(MenuViewModel.TAG, "onError: ${it.printStackTrace()}") }
            )
Haomin
  • 1,265
  • 13
  • 12
0

I know It's not the best way or even a professional way but if you do not hav time to do it the right way you can recreate the MutableLiveDataa after you observed it. it would be like :

    private void purchaseAllResultFun() {
    viewModel.isAllPurchaseSuccess().observe(getViewLifecycleOwner(), isSuccess -> {
        if (!isSuccess) {
            failedPurchaseToast();
        }else {
            successfulPurchaseToast();
        }
        //reset mutableLiveData after you're done
        viewModel.resetIsAllSuccessFull();
    });
}

//function in viewmodel class
public void resetIsAllSuccessFull(){
    purchaseRepository.reSetIsAllSuccessFull();
}

//function in repository class
public void resetIsAllSuccessFull(){
    successLiveData = new MutableLiveData<>();
}

In this way if you need to recall purchaseAllResultFun() function it won't give the stored value.

StackOverflower
  • 369
  • 2
  • 12
0

Solution worked for me :

class SingleLiveData<T> : MutableLiveData<T>() {

override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
    super.observe(owner, Observer { t ->
        if (t != null) {
            observer.onChanged(t)
            value=null
        }
    })
}}'
0

I had the same issue. Old data kept re-appearing.

Reinitialising the MutableLiveData worked for me on onCreate()

on ViewModel :

public MutableLiveData<CityResponse.CityBean> cityCollabMapLiveData = new MutableLiveData<>();

Reinitialising (onCreate() of the fragment):

viewModel.cityCollabMapLiveData = new MutableLiveData<>();
binding.etCollabCity.setText("");
TharakaNirmana
  • 10,237
  • 8
  • 50
  • 69