导航控制器在实时数据观察器中被调用两次
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)
}
}
基本上,我们在这里验证我们还没有到达目的地。这可以通过将先前的动作目标与当前目标进行比较来完成。让我知道代码是否有帮助!
我想做的是在 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)
}
}
基本上,我们在这里验证我们还没有到达目的地。这可以通过将先前的动作目标与当前目标进行比较来完成。让我知道代码是否有帮助!