Как очистить сохраненную ценность LiveData?
Согласно Документация LiveData:
Класс LiveData предоставляет следующие преимущества:
...
Всегда обновляемые данные: Если жизненный цикл снова запускается (например, операция возвращается к запущенному состоянию из заднего стека), он получает последние данные о местоположении (если он еще не был).
Но иногда мне не нужна эта функция.
Например, я вижу LiveData в ViewModel и Observer в действии:
//LiveData
val showDialogLiveData = MutableLiveData<String>()
//Activity
viewModel.showMessageLiveData.observe(this, android.arch.lifecycle.Observer { message ->
AlertDialog.Builder(this)
.setMessage(message)
.setPositiveButton("OK") { _, _ -> }
.show()
})
Теперь после каждого поворота появится старое диалоговое окно.
Есть ли способ очистить сохраненное значение после его обработки или это неправильное использование LiveData?
Ответы
Ответ 1
Обновить
Есть несколько способов решить эту проблему. Они кратко изложены в статье LiveData с SnackBar, Navigation и другими событиями (случай SingleLiveEvent). Это написано сотрудником Googler, который работает с командой компонентов архитектуры.
TL; DR Более надежный подход заключается в использовании класса-обертки Event, пример которого вы можете увидеть внизу статьи.
Этот шаблон попал в многочисленные образцы Android, например:
Почему оболочка Event предпочтительнее SingleLiveEvent?
Одна проблема с SingleLiveEvent состоит в том, что если существует несколько наблюдателей для SingleLiveEvent, только один из них будет уведомлен об изменении этих данных - это может привести к незначительным ошибкам и их трудно обойти.
Используя класс-оболочку Event, вы можете либо явно "обрабатывать" контент (контент может быть "обработан" только один раз), либо просматривать контент, который всегда возвращает то, что было последним "контентом". В примере диалога это означает, что вы всегда можете увидеть, что было последним сообщением с peek
, но убедитесь, что для каждого нового сообщения диалог запускается только один раз, используя getContentIfNotHandled
.
Старый ответ
Ответ Алексея в комментариях, я думаю, что именно то, что вы ищете. Там пример кода для класса под названием SingleLiveEvent. Цель этого класса описывается как:
Наблюдаемая жизненным циклом наблюдаемая, которая отправляет только новые обновления после подписки, используемая для таких событий, как навигация и сообщения Snackbar.
Это позволяет избежать общей проблемы с событиями: при изменении конфигурации (например, при вращении) обновление может быть отправлено, если наблюдатель активен. Эта LiveData вызывает наблюдаемое только в случае явного вызова setValue() или call().
Ответ 2
В моем случае SingleLiveEvent не помогает. Я использую этот код:
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);
}
}
};
Ответ 3
Я не уверен, будет ли это работать в вашем случае, но в моем случае (увеличение/уменьшение количества элементов в комнате путем нажатия на представления) удаление Observer и проверка наличия активных наблюдателей позволяют мне выполнить эту работу:
LiveData<MenuItem> menuitem = mViewModel.getMenuItemById(menuid);
menuitem.observe(this, (MenuItem menuItemRoom) ->{
menuitem.removeObservers(this);
if(menuitem.hasObservers())return;
// Do your single job here
});
});
ОБНОВЛЕНИЕ 20/03/2019:
Теперь я предпочитаю это: класс EventWraper из Google Samples внутри 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;
}
}
В 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;
}
В Деятельности/Фрагмент
mViewModel
.onSaveEvent()
.observe(
getViewLifecycleOwner(),
booleanEvent -> {
if (booleanEvent != null)
final Boolean shouldSave = booleanEvent.getContentIfNotHandled();
if (shouldSave != null && shouldSave) saveData();
}
});
Ответ 4
Вы должны использовать SingleLiveEvent для этого случая
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"
}
}
И внутри вас viewmodel класс создать объект, как:
val snackbarMessage = SingleLiveEvent<Int>()
Ответ 5
Если вам нужно простое решение, попробуйте это:
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)
}
})
}
}
Используйте его как обычный MutableLiveData