导航控制器在实时数据观察器中被调用两次

Navigation controller gets called twice inside livedata observer

我想做的是在 LiveData 观察器中使用导航控制器,因此当用户单击列表中的项目时,它会通知 ViewModel,然后 ViewModel 更新数据,当发生这种情况时,片段观察到这个并导航到下一个。

我的问题是,出于某种原因,观察者被调用了两次,第二次我得到一个异常,说这个 NavController 不知道目的地。

点击我的片段:

override fun onClick(view: View?) {
        viewModel.productSelected.observe(viewLifecycleOwner, Observer<ProductModel> {
            try {
                this.navigationController.navigate(R.id.action_product_list_to_product_detail)
            } catch (e: IllegalArgumentException) { }
        })

        val itemPosition = view?.let { recyclerView.getChildLayoutPosition(it) }
        viewModel.onProductSelected(listWithHeaders[itemPosition!!].id)
    }

在我的 ViewModel 中:

fun onProductSelected(productId: String) {
    productSelected.value = getProductById(productId)
}

它被调用了两次,因为首先你订阅了,所以你得到了一个默认值,然后你在你的 productSelected LiveData 中更改了一个值,所以你的观察者再次收到通知。 因此,开始观察 onProductSelected被调用如下:

override fun onClick(view: View?) {
    val itemPosition = view?.let { recyclerView.getChildLayoutPosition(it) }
    viewModel.onProductSelected(listWithHeaders[itemPosition!!].id)

    viewModel.productSelected.observe(viewLifecycleOwner, Observer<ProductModel> {
        try {                                
          this.navigationController.navigate(R.id.action_product_list_to_product_detail)
            } catch (e: IllegalArgumentException) { }
    })
}

再次注意,一旦您开始观察您的 LiveData,它就会在每次 productSelected 更改时收到通知。这是你想要的吗?如果没有,那么您应该在观察者使用一次后将其删除。

捕获异常可能有效,但它也可能让您错过其他几个问题。最好用目的地检查当前布局以验证用户是否已经在那里。我更喜欢的另一种选择是检查以前的目的地,例如:

fun Fragment.currentDestination() = findNavController().currentDestination

fun Fragment.previousDestination() = findNavController().previousBackStackEntry?.destination

fun NavDestination.getDestinationIdFromAction(@IdRes actionId: Int) = getAction(actionId)?.destinationId

private fun Fragment.isAlreadyAtDestination(@IdRes actionId: Int): Boolean {
    val previousDestinationId = previousDestination()?.getDestinationIdFromAction(actionId)
    val currentDestinationId = currentDestination()?.id
    return previousDestinationId == currentDestinationId
}

fun Fragment.navigate(directions: NavDirections) {
    if (!isAlreadyAtDestination(directions.actionId)) {
        findNavController().navigate(directions)
    }
}

基本上,我们在这里验证我们还没有到达目的地。这可以通过将先前的动作目标与当前目标进行比较来完成。让我知道代码是否有帮助!