LiveData类具有以下优点:
...
始终是最新数据:如果生命周期再次启动(如从后端堆栈返回到启动状态的活动),它将收到最新的位置数据(如果尚未生成)。
但有时候我不需要这个功能。
例如,我在ViewModel中跟随LiveData,在Activity中跟踪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?
实际上有几种方法可以解决这个问题。它们在文章LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case)中得到了很好的总结。这是由一位与架构组件团队合作的Google员工撰写的。
TL; DR更强大的方法是使用Event wrapper class,你可以看到the article底部的一个例子。
这种模式已经进入了许多Android样本,例如:
为什么Event包装器比SingleLiveEvent更受欢迎?
SingleLiveEvent的一个问题是,如果SingleLiveEvent有多个观察者,那么当数据发生变化时,只会通知其中一个 - 这可能会引入细微的错误并且难以解决。
使用Event包装器类,将正常通知所有观察者。然后,您可以选择明确“处理”内容(内容仅“处理”一次)或查看内容,内容始终返回最新的“内容”。在对话框示例中,这意味着您始终可以查看peek
的最后一条消息,但确保对于每条新消息,仅使用getContentIfNotHandled
触发一次对话框。
亚历克斯在评论中的回答是我认为你正在寻找什么。有一个名为SingleLiveEvent的类的示例代码。本课程的目的描述如下:
生命周期感知的observable,仅在订阅后发送新的更新,用于导航和Snackbar消息等事件。
这避免了事件的常见问题:在配置更改(如旋转)时,如果观察者处于活动状态,则可以发出更新。如果显式调用setValue()或call(),则此LiveData仅调用observable。
在我的情况下,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);
}
}
};
在这种情况下,您需要使用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>()
我不确定它是否适用于你的情况,但在我的情况下(通过点击视图增加/减少房间数量)删除观察者并检查是否有活跃的观察者让我做这项工作:
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:
现在我更喜欢这个:来自MutableLiveData内的Google Samples的EventWraper类
/**
* 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();
}
});
如果您需要简单的解决方案,请试试这个:
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一样使用它
我找到的最佳解决方案是live event library,如果你有多个观察者,它可以很好地工作:
class LiveEventViewModel : ViewModel() {
private val clickedState = LiveEvent<String>()
val state: LiveData<String> = clickedState
fun clicked() {
clickedState.value = ...
}
}