导航控制器在livedata观察器中被调用两次

时间:2019-11-17 19:40:36

标签: android kotlin mvvm navigation android-livedata

我想做的是在LiveData观察器中使用Navigation控制器,因此,当用户单击列表中的项目时,它会通知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)
}

2 个答案:

答案 0 :(得分:1)

之所以调用它两次,是因为首先订阅,然后返回默认值,然后在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更改时都会收到通知。是你想要的吗?如果没有,那么一旦使用过的观察者就应该删除。

答案 1 :(得分:1)

捕获异常可能有用,但是它也会使您错过其他一些问题。最好与目标位置一起检查当前布局,以验证用户是否已经在该位置。我更喜欢的另一种选择是检查先前的目的地,例如:

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)
    }
}

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