为什么在我导航回片段时调用 onChanged?
Why is onChanged being called when I navigate back to a fragment?
我有一个 ViewModel 处理我的业务逻辑,我正在使用 Koin 将其注入我的 activity 和我的每个片段。但是,在我从 Fragment A - Fragment B 导航回 Fragment A 后,我的观察者再次被触发。为什么会发生这种情况?如何在返回时停止触发此 onChanged?
我已经尝试将 'this' 和 'viewLifecycleOwner' 都设置为 LiveData 的 LifecycleOwner。
我也试过将 observable 移动到 onCreate、onActivityCreated 和 onViewCreated
我的视图模型:
class MyViewModel : ViewModel() {
private val _myData = MutableLiveData<Data>()
val myData = LiveData<Data>()
get() = _myData
fun doSomething() {
... // some code
_myData.postValue(myResult)
}
我的活动:
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...
})
}
}
当我从 Fragment A -> Fragment B 导航时,一切都如我所料。但是,当我从片段 B 导航回片段 A(通过按后退按钮)时,会调用 myData 上 Observer 的 onChanged 方法,然后导航移回导航 B。
这是使用 MutableLiveData
时的预期行为。我认为您的问题与在哪里添加或删除订阅者无关。
MutableLiveData
保留最后设置的值。
当我们回到上一个片段时,我们的 LiveData
观察器会再次收到现有值 的通知。这是为了保留片段的状态,这就是 LiveData
的确切目的。
Google 本身已经解决了这个问题并提供了一种方法来覆盖此行为,即使用 Event wrapper.
- 创建包装器来包装事件。
- 创建一个观察者来观察这个包装事件。
让我们看看实际的代码
- 创建包装器事件
/**
* 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
}
这是预料之中的,因为您正在重新订阅 LiveData,并且 LiveData 在您订阅它时会重播其最后发出的值。
您应该使用不会在重新订阅时重播最后发出的值的不同组件,例如,您可以改用 https://github.com/Zhuinden/live-event。
我最终做了一些比尝试更改为包装事件更简单的事情。
切记,我的解决方案完全符合我的情况,虽然这不是推荐的方式,但我们使用这个解决方案是为了快速提供我们想要的结果。
我的问题是一样的,当我向后导航时,LiveData(我正在使用 Flow)重新触发了与离开片段时相同的值。这是一个问题,因为它触发了观察者,然后观察者读取值的状态,如果状态设置为 COMPLETE,我们将导航到下一页。
如果你有类似的东西,你从源头观察某种信息来做出决定,这当然会起作用。
无论如何,我的解决方案是导航然后将源数据设置为默认值,即状态为 NONE.
的对象
这阻止了观察者在从后退按钮导航到时触发最后一个状态。
我在 viewModel 中创建了函数
fun clearCustomerSearch() {
customerSearchStateFlow.value = HttpResponse.none()
}
这会触发观察者,但是它什么都不做,因为状态是 NONE。
希望这对不想使用事件包装的人有所帮助,尽管对于长期和更完整的解决方案,我会看看 Jose 的回答。
我有一个 ViewModel 处理我的业务逻辑,我正在使用 Koin 将其注入我的 activity 和我的每个片段。但是,在我从 Fragment A - Fragment B 导航回 Fragment A 后,我的观察者再次被触发。为什么会发生这种情况?如何在返回时停止触发此 onChanged?
我已经尝试将 'this' 和 'viewLifecycleOwner' 都设置为 LiveData 的 LifecycleOwner。
我也试过将 observable 移动到 onCreate、onActivityCreated 和 onViewCreated
我的视图模型:
class MyViewModel : ViewModel() {
private val _myData = MutableLiveData<Data>()
val myData = LiveData<Data>()
get() = _myData
fun doSomething() {
... // some code
_myData.postValue(myResult)
}
我的活动:
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...
})
}
}
当我从 Fragment A -> Fragment B 导航时,一切都如我所料。但是,当我从片段 B 导航回片段 A(通过按后退按钮)时,会调用 myData 上 Observer 的 onChanged 方法,然后导航移回导航 B。
这是使用 MutableLiveData
时的预期行为。我认为您的问题与在哪里添加或删除订阅者无关。
MutableLiveData
保留最后设置的值。
当我们回到上一个片段时,我们的 LiveData
观察器会再次收到现有值 的通知。这是为了保留片段的状态,这就是 LiveData
的确切目的。
Google 本身已经解决了这个问题并提供了一种方法来覆盖此行为,即使用 Event wrapper.
- 创建包装器来包装事件。
- 创建一个观察者来观察这个包装事件。
让我们看看实际的代码
- 创建包装器事件
/**
* 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
}
这是预料之中的,因为您正在重新订阅 LiveData,并且 LiveData 在您订阅它时会重播其最后发出的值。
您应该使用不会在重新订阅时重播最后发出的值的不同组件,例如,您可以改用 https://github.com/Zhuinden/live-event。
我最终做了一些比尝试更改为包装事件更简单的事情。
切记,我的解决方案完全符合我的情况,虽然这不是推荐的方式,但我们使用这个解决方案是为了快速提供我们想要的结果。
我的问题是一样的,当我向后导航时,LiveData(我正在使用 Flow)重新触发了与离开片段时相同的值。这是一个问题,因为它触发了观察者,然后观察者读取值的状态,如果状态设置为 COMPLETE,我们将导航到下一页。
如果你有类似的东西,你从源头观察某种信息来做出决定,这当然会起作用。
无论如何,我的解决方案是导航然后将源数据设置为默认值,即状态为 NONE.
的对象这阻止了观察者在从后退按钮导航到时触发最后一个状态。
我在 viewModel 中创建了函数
fun clearCustomerSearch() {
customerSearchStateFlow.value = HttpResponse.none()
}
这会触发观察者,但是它什么都不做,因为状态是 NONE。
希望这对不想使用事件包装的人有所帮助,尽管对于长期和更完整的解决方案,我会看看 Jose 的回答。