当我导航回片段时为什么调用onChanged?

时间:2019-10-11 13:14:37

标签: android android-architecture-components android-livedata

我有一个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。

3 个答案:

答案 0 :(得分:2)

使用MutableLiveData时,这是预期的行为。我认为您的问题与在何处添加或删除订户无关。

MutableLiveData保存设置的最后一个值。 当我们返回上一个片段时,我们的LiveData观察值会再次以现有值得到通知。这是为了保留片段的状态,而这正是LiveData的目的。

Google本身已经解决了这一问题,并提供了一种使用事件包装器来覆盖此行为的方法。

  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. 在View模型中声明实时数据对象吗?
// 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

答案 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 的回答。