为什么在我导航回片段时调用 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.

  1. 创建包装器来包装事件。
  2. 创建一个观察者来观察这个包装事件。

让我们看看实际的代码

  1. 创建包装器事件
/**
* 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
}
  1. 创建观察者
class EventObserver<T>(private val onEventUnhandledContent: (T) -> Unit) : Observer<Event<T>> {
 override fun onChanged(event: Event<T>?) {
     event?.getContentIfNotHandled()?.let { value ->
         onEventUnhandledContent(value)
     }
 }
}
  1. 在视图模型中声明实时数据对象?
// Declare live data object
val testLiveData: MutableLiveData<Event<Boolean>
             by lazy{ MutableLiveData<Event<Boolean>>() }
  1. 为实时数据对象设置数据
testLiveData.postValue(Event(true))
  1. 观察片段中的实时数据
viewModel?.testLiveData?.observe(this, EventObserver { result ->
 // Your actions
}

详细参考:https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150

这是预料之中的,因为您正在重新订阅 LiveData,并且 LiveData 在您订阅它时会重播其最后发出的值。

您应该使用不会在重新订阅时重播最后发出的值的不同组件,例如,您可以改用 https://github.com/Zhuinden/live-event

我最终做了一些比尝试更改为包装事件更简单的事情。

切记,我的解决方案完全符合我的情况,虽然这不是推荐的方式,但我们使用这个解决方案是为了快速提供我们想要的结果。

我的问题是一样的,当我向后导航时,LiveData(我正在使用 Flow)重新触发了与离开片段时相同的值。这是一个问题,因为它触发了观察者,然后观察者读取值的状态,如果状态设置为 COMPLETE,我们将导航到下一页。

如果你有类似的东西,你从源头观察某种信息来做出决定,这当然会起作用。

无论如何,我的解决方案是导航然后将源数据设置为默认值,即状态为 NONE.

的对象

这阻止了观察者在从后退按钮导航到时触发最后一个状态。

我在 viewModel 中创建了函数

fun clearCustomerSearch() {
    customerSearchStateFlow.value = HttpResponse.none()
}

这会触发观察者,但是它什么都不做,因为状态是 NONE。

希望这对不想使用事件包装的人有所帮助,尽管对于长期和更完整的解决方案,我会看看 Jose 的回答。