Android架构组件ViewModel和LiveData在屏幕旋转后触发

19

在使用 ViewModelLiveData 架构组件时,我遇到了问题。当使用 fragment 并旋转屏幕时,观察者会被触发...

我尝试在所有的 fragment 生命周期方法中移动 viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java),但没有成功。

我的场景相对简单:

  1. 带有电子邮件和密码的登录界面
  2. 用户点击“登录”按钮
  3. viewmodel 调用 login(email, password) 并设置 LiveData 对象的值
  4. 现在只需显示 Toast

此时一切都正常。但是当我旋转屏幕时,Toast 再次出现,而没有任何用户交互。

我需要在 onDestroyView() 中做些什么吗?

提前感谢!


你能否提供更多信息?我想知道当你点击按钮时如何调用登录方法,以及ViewModel内部的该方法在做什么。 - 4gus71n
当然!今天会完成。谢谢! - Nicolas Jafelle
2个回答

15

好的,最终找到了问题所在以及解决方法。LiveData不适用于单一事件。因此,有几种方法可以解决或处理它,这两个链接对我很有用:

Jose Alcérreca解决此问题的文章

Jose Alcérreca的EventObserver

Jose Alcérreca的SingleLiveEvent类

基本上:

在ViewModel中:

var eventLiveData: MutableLiveData<Event<ErrorResponse>> = MutableLiveData()

在Activity或Fragment中:

viewModel.eventLiveData.observe(this, EventObserver {
     it?.let {
          shortToast(it.message)
     }
})

@Thracian,请查看这个! - Nicolas Jafelle
我正在使用LiveData作为从我的xml > ViewModel > Fragment的点击监听器。如果在监听点击后将值设置为null,则在屏幕旋转时观察到LiveData为空。您必须在Fragment中检查数据是否为空。这是一个hack,但有效。fun contentClicked(content: Content) { contentSelected.value = content; contentSelected.value = null; } - AdamHurwitz

5
这是LiveData和ViewModel的工作方式。当您调用ViewModelProviders.of(this).get(MainViewModel :: class.java)时,您将获得相同的ViewModel和相同的LiveData,并且LiveData具有先前的对象(例如User)和先前的凭据。 您可以在 onPause() onStop()中重置LiveData的用户以将其重置为初始状态。
我不知道您如何调用toast。如果您能分享您的ViewModel和MainActivity,我可以更具体地说明。

我正在将ViewModel作为Presenter使用。在那里执行所有的业务逻辑,例如调用登录端点、保存缓存并将JSON数据传递给livedata.value以触发观察者。 - Nicolas Jafelle
这就是你应该如何使用ViewModel,没问题。有些例子也会像Presenter一样使用它,将View的弱引用作为接口发送以调用Activity方法或使用LiveData状态来调用方法。我的意思是,ViewModel的好处在于旋转后可以获得相同的ViewModel,其中包含先前的状态和数据。在你的情况下,是LiveData<User>。你应该重置User并在MutableLiveData<User>上调用setValue(livedata.getUser())。并且要在旋转之前的onPause()或onStop()中执行。 - Thracian
直到ViewModel被销毁之前,它才会被清除。您应该查看ViewModel生命周期。ViewModel的好处是能够在旋转后或在不同的片段中使用它进行片段间通信。您可以在某个片段中设置LiveData的setValue并在其他片段中观察它,它将保留其值和状态,直到onDestroy()和ViewModel的onCleared()方法被调用。这个链接和这个链接可能会有所帮助。 - Thracian
上面的解决方案有效。关于在 onPause() 中将 LiveData 设置为空的问题,我的评论中的解决方案也可以工作,但这个更好,因为你不需要检查 null 状态,这有点像是一种 hack(黑科技)。 - AdamHurwitz
忽略,这两种解决方案都需要检查 null 状态,无论是在 ViewModel 中像我上面所做的那样清除 LiveData 还是在 Fragment/Activity 生命周期中清除。正如 Jose 在 Medium 中所述,这两种策略都不推荐使用。 - AdamHurwitz
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接