我有一个ViewModel处理我的业务逻辑,并且我正在使用Koin将其注入到我的活动和每个片段中。但是,当我从片段A-片段B导航并回到片段A后,我的观察者再次被触发。为什么会发生这种情况,以及当我向后导航时如何停止onChanged触发?
我尝试将'this'和'viewLifecycleOwner'设置为LiveData的LifecycleOwner。
我还尝试将可观察对象移动到onCreate,onActivityCreated和onViewCreated
我的ViewModel:
class MyViewModel : ViewModel() {
private val _myData = MutableLiveData<Data>()
val myData = LiveData<Data>()
get() = _myData
fun doSomething() {
... // some code
_myData.postValue(myResult)
}
MyActivity:
class Activity : BaseActivity() {
private val viewModel by viewModel<MyViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
setSupportActionBar(main_toolbar)
subscribeUI()
}
private fun subscribeUI() {
myViewModel.isLoading.observe(this, Observer {
toggleProgress(it)
})
}
}
片段A:
class FragmentA : BaseFragment() {
private val viewModel by sharedViewModel<MyViewModel>()
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
subscribeUI()
}
private fun subscribeUI() {
viewModel.myData.observe(viewLifecycleOwner, Observer {
val direction =
FragmentADirections.actionAtoB()
mainNavController?.navigate(direction)
})
}
}
片段B:
class FragmentB : BaseFragment() {
private val authViewModel by sharedViewModel<LoginViewModel>()
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
subscribeUI()
}
private fun subscribeUI() {
viewModel.otherData.observe(viewLifecycleOwner, Observer {
// Do something else...
})
}
}
当我从片段A->片段B导航时,一切正常。但是,当我从片段B导航回片段A时(通过按“后退”按钮),将调用myData上的Observer中的onChanged方法,并将导航移回导航B。
答案 0 :(得分:2)
使用MutableLiveData
时,这是预期的行为。我认为您的问题与在何处添加或删除订户无关。
MutableLiveData
保存设置的最后一个值。
当我们返回上一个片段时,我们的LiveData
观察值会再次以现有值得到通知。这是为了保留片段的状态,而这正是LiveData
的目的。
Google本身已经解决了这一问题,并提供了一种使用事件包装器来覆盖此行为的方法。
让我们看看运行中的代码
/**
* Used as a wrapper for data that is exposed via a LiveData that represents an event.
*/
open class Event<out T>(private val content: T) {
var hasBeenHandled = false
private set // Allow external read but not write
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}
class EventObserver<T>(private val onEventUnhandledContent: (T) -> Unit) : Observer<Event<T>> {
override fun onChanged(event: Event<T>?) {
event?.getContentIfNotHandled()?.let { value ->
onEventUnhandledContent(value)
}
}
}
// Declare live data object
val testLiveData: MutableLiveData<Event<Boolean>
by lazy{ MutableLiveData<Event<Boolean>>() }
testLiveData.postValue(Event(true))
viewModel?.testLiveData?.observe(this, EventObserver { result ->
// Your actions
}
答案 1 :(得分:0)
这是预期的,因为您正在重新订阅LiveData,并且LiveData在订阅时重播其最后发出的值。
您应该使用其他组件,该组件不会在重新订阅时重播最后发出的值,例如,您可以使用https://github.com/Zhuinden/live-event。
答案 2 :(得分:0)
我最终做了一些比尝试更改为包装事件更简单的事情。
请记住,我的解决方案完全适用于我的情况,虽然它不是推荐的方式,但我们使用此解决方案是为了快速获得我们想要的结果。
我的问题是一样的,当我导航回 LiveData(我正在使用 Flow)时,它重新触发了与离开片段时相同的值。这是一个问题,因为它会触发观察者,然后观察者读取值的状态,如果状态设置为 COMPLETE,我们将导航到下一页。
如果您有类似的事情,您可以从源头观察某种信息以做出决定,那么这当然会奏效。
无论如何,我的解决方案是导航到然后将源数据设置为默认值,即状态为 NONE 的对象。
这阻止了观察者在从后退按钮导航到时触发最后一个状态。
我在 viewModel 中创建了函数
fun clearCustomerSearch() {
customerSearchStateFlow.value = HttpResponse.none()
}
这会触发观察者,但是它什么也不做,因为状态是 NONE。
希望这对不想使用事件包装的人有所帮助,尽管对于长期和更完整的解决方案,我会看看 Jose 的回答。