我有一个LiveData在我的ViewModel:-
private val _toastMessage = MutableLiveData<Long>()
val toastMessage
get() = _toastMessage
这是我改变它的值的唯一方法(点击片段中的提交按钮):-
fun onSubmitClicked(<params>){
Log.i(LOG_TAG, "submit button clicked")
uiScope.launch {
if(!myChecksForEditTextValuesSucceeded())
{
_toastMessage.value = 0
}else{
_toastMessage.value = 1
}
}
}
在片段中,我为这个LiveData有一个观察者:-
transactionViewModel.toastMessage.observe(viewLifecycleOwner, Observer { it->
when{
(it.compareTo(0) == 0) -> Toast.makeText(context, resources.getString(R.string.toast_msg_transaction_not_inserted), Toast.LENGTH_SHORT).show()
else -> Toast.makeText(context, resources.getString(R.string.toast_msg_transaction_inserted), Toast.LENGTH_SHORT).show()
}
})
理想情况下,我希望这个观察者的onChange只在点击我的片段上的提交按钮时被调用。但是,正如我所看到的,它也被调用,甚至在我的片段的onCreateView。
这可能的原因是什么?
问题是,LiveData
推新的值,而你观察它,但它也推最近的值当你第一次观察它,或者如果观察者的Lifecycle
恢复和数据已经改变,因为它被暂停。
所以当你将toastMessage
的值设置为1
时,它会保持这种状态-并且ViewModel
的寿命比Fragment
的寿命更长(这就是重点!)所以当你的Fragment
被重新创建时,它的observe
是toastMessage
的当前值,看到它当前是1
,并显示Toast。
问题是您不想将其用作持久的数据状态—您希望它是一个一次性事件,当您观察它时使用,因此Toast仅在响应按钮按下时显示一次。这是LiveData
棘手的事情之一,已经有一堆的解决方案,类,库等围绕使其工作
这里有一个来自Android开发人员的旧帖子,讨论这个用例的问题,以及可用的解决方法和他们不足的地方-如果有人感兴趣的话!但就像上面说的,这些都过时了,他们建议遵循官方指南。
官方的说法基本上是:
- 在ViewModel上触发事件
- VM更新UI状态,包括要显示的消息
- UI观察到这个更新,显示消息,并通知VM它已被显示
- VM使用删除的消息更新UI状态
这不是处理可消耗事件的唯一方法,但这是他们推荐的方法,而且相当简单。所以你会想这样做:
// making this nullable so we can have a "no message" state
private val _toastMessage = MutableLiveData<Long?>(null)
// you should specify the type here btw, as LiveData instead of MutableLiveData -
// that's the reason for making the Mutable reference private and having a public version
val toastMessage: LiveData<Long?>
get() = _toastMessage
// call this when the current message has been shown
fun messageDisplayed() {
_toastMessage.value = null
}
// make a nice display function to avoid repetition
fun displayToast(@StringRes resId: Int) {
Toast.makeText(context, resources.getString(resId), Toast.LENGTH_SHORT).show()
// remember to tell the VM it's been displayed
transactionViewModel.messageDisplayed()
}
transactionViewModel.toastMessage.observe(viewLifecycleOwner, Observer { it->
// if the message code is null we just don't do anything
when(it) {
0 -> displayToast(R.string.toast_msg_transaction_not_inserted)
1 -> displayToast(R.string.toast_msg_transaction_inserted)
}
})
你可能还想创建一个Toast状态的enum,而不是仅仅使用数字,这样更容易读——你甚至可以把它们的字符串id放在enum中:
enum class TransactionMessage(@StringRes val stringId: Int) {
INSERTED(R.string.toast_msg_transaction_inserted),
NOT_INSERTED(R.string.toast_msg_transaction_not_inserted)
}
private val _toastMessage = MutableLiveData<TransactionMessage?>(null)
val toastMessage: LiveData<TransactionMessage?>
get() = _toastMessage
uiScope.launch {
if(!myChecksForEditTextValuesSucceeded()) toastMessage.value = NOT_INSERTED
else _toastMessage.value = INSERTED
}
transactionViewModel.toastMessage.observe(viewLifecycleOwner, Observer { message ->
message?.let { displayToast(it.stringId) }
// or if you're not putting the string resource IDs in the enum:
when(message) {
NOT_INSERTED -> displayToast(R.string.toast_msg_transaction_not_inserted)
INSERTED -> displayToast(R.string.toast_msg_transaction_inserted)
}
})
与仅仅使用数字相比,它可以更清晰和自我记录,你知道吗?