仅在单击按钮时更改livedata的值。但是,即使在Kotlin Android中的片段上,观察者也被命名

发布于 2025-02-12 10:18:22 字数 1106 浏览 1 评论 0原文

我的ViewModel中有一个Livedata: -

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()
            }
        })

理想情况下,我是我的。希望只在单击我的片段上的提交按钮时,就会称呼该观察者的偏移。但是,正如我所看到的,即使在我的片段上,它也会被调用。

这可能是什么原因?

I have a LiveData in my ViewModel:-

private val _toastMessage = MutableLiveData<Long>()
val toastMessage
    get() = _toastMessage

And this is the only way I am changing it's value(on click of a submit button in the fragment):-

fun onSubmitClicked(<params>){
        Log.i(LOG_TAG, "submit button clicked")
        uiScope.launch {
            if(!myChecksForEditTextValuesSucceeded())
            {
                _toastMessage.value = 0
            }else{
                _toastMessage.value = 1
            }
        }
    } 

And in the fragment, I have an observer for this 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()
            }
        })

Ideally, I am expecting the onChange of this Observer to be called only on clicking the submit button on my fragment. But, as I can see, it is also getting called even on onCreateView of my fragment.

What could be the possible reasons for this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

作妖 2025-02-19 10:18:23

问题是livedata在观察它时 new 值,但是当您 first 时,它也会推动最新值 观察它,或者如果观察者的生命周期 简历,并且自暂停以来数据发生了变化。

因此,当您将toastMessage的值设置为1时,它保持这种方式 - 和viewModel s的寿命比fragment < /code> s(这是全部!),因此,当您的fragment重新创建时,它observer s s toastMessage,看到当前是1,并显示敬酒。


问题是您不想将其用作持久的数据状态 - 您希望它是一个单发事件,当您观察它时,您会消耗对按钮的响应。 很多解决方法,类,图书馆等

这是关于livedata的棘手之处之一,并且围绕使它起作用的构建有 /livedata-with-snackbar-nackativation and the-events-singleliveEvent-case-ac262673150“ rel =“ nofollow noreferrer”>旧帖子来自Android开发人员之一,与此用例讨论问题,可用的解决方法以及它们不足的地方 - 如果任何人有兴趣!但是就像它在顶部所说的那样,这一切都已经过时了,他们建议按照官方指南

官方方式基本上是:

  • 某些东西在ViewModel上触发了一个事件。VM
  • 更新UI状态,包括要显示的消息,
  • UI观察此更新,显示消息并告知VM已显示的
  • VM更新UI状态,并使用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)
    }
})

您可能还需要创建敬酒状态的枚举,而不仅仅是使用数字,更可读的方式 - 您甚至可以将其字符串ID放入枚举中:

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)
    }
})

它可以更清楚,并且与仅使用数字相比,自我记录了吗?

The issue is that LiveData pushes new values while you're observing it, but it also pushes the most recent value when you first observe it, or if the observer's Lifecycle resumes and the data has changed since it was paused.

So when you set toastMessage's value to 1, it stays that way - and ViewModels have a longer lifetime than Fragments (that's the whole point!) so when your Fragment gets recreated, it observes the current value of toastMessage, sees that it's currently 1, and shows a Toast.


The problem is you don't want to use it as a persistent data state - you want it to be a one-shot event that you consume when you observe it, so the Toast is only shown once in response to a button press. This is one of the tricky things about LiveData and there have been a bunch of workarounds, classes, libraries etc built around making it work

There's an old post here from one of the Android developers discussing the problem with this use case, and the workarounds available and where they fall short - in case anyone is interested! But like it says at the top, that's all outdated, and they recommend following the official guidelines.

The official way basically goes:

  • something triggers an event on the ViewModel
  • the VM updates the UI state including a message to be displayed
  • the UI observes this update, displays the message, and informs the VM it's been displayed
  • the VM updates the UI state with the message removed

That's not the only way to handle consumable events, but it's what they're recommending, and it's fairly simple. So you'd want to do something like this:

// 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)
    }
})

You also might want to create an enum of Toast states instead of just using numbers, way more readable - you can even put their string IDs in the 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)
    }
})

It can be a bit clearer and self-documenting compared to just using numbers, y'know?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文