当我试图从一个片段导航到另一个片段时,我遇到了新的Android导航架构组件的问题,我得到了这个奇怪的错误:

java.lang.IllegalArgumentException: navigation destination XXX
is unknown to this NavController

其他导航都很好,除了这个。

我使用Fragment的findNavController()函数来访问NavController。

任何帮助都将不胜感激。


当前回答

将我的答案优雅地扔到处理两种情况(双击,同时点击两个按钮)的环中,但尽量不掩盖真正的错误。

我们可以使用navigateSafe()函数来检查我们试图导航到的目的地从当前目的地是否是无效的,但从前一个目的地是否是有效的。如果是这种情况,代码假设用户双击或同时点击两个按钮。

然而,这个解决方案并不完美,因为它可能会掩盖一些小众情况下的实际问题,即我们试图导航到恰好是父端的目的地。但据推测,这种情况不太可能发生。

代码:

fun NavController.navigateSafe(directions: NavDirections) {
    val navigateWillError = currentDestination?.getAction(directions.actionId) == null

    if (navigateWillError) {
        if (previousBackStackEntry?.destination?.getAction(directions.actionId) != null) {
            // This is probably some user tapping two different buttons or one button twice quickly
            // Ignore...
            return
        }

        // This seems like a programming error. Proceed and let navigate throw.
    }

    navigate(directions)
}

其他回答

似乎混合fragmentManager控件的backstack和Navigation Architecture控件的backstack也会导致这个问题。

例如,最初的CameraX基本示例使用fragmentManager后台导航,如下所示,它似乎没有正确地与导航交互:

// Handle back button press
        view.findViewById<ImageButton>(R.id.back_button).setOnClickListener {
            fragmentManager?.popBackStack()
        }

如果你在从主片段(在本例中是相机片段)移动之前记录这个版本的“当前目的地”,然后当你返回主片段时再次记录它,你可以从日志中的id中看到id是不相同的。据猜测,导航在移动到片段时更新了它,而fragmntManager在移动回来时没有再次更新它。日志显示:

修改前:D/CameraXBasic: currentDest?: androidx.navigation.fragment.FragmentNavigator Destination@b713195美元 After: D/CameraXBasic: currentDest?: androidx.navigation.fragment.FragmentNavigator Destination@9807d8f美元

更新版的CameraX基本示例使用导航返回如下:

 // Handle back button press
        view.findViewById<ImageButton>(R.id.back_button).setOnClickListener {
            Navigation.findNavController(requireActivity(), R.id.fragment_container).navigateUp()
        }

这可以正常工作,当返回到主片段时,日志显示相同的id。

修改前:D/CameraXBasic: currentDest?: androidx.navigation.fragment.FragmentNavigator Destination@b713195美元 After: D/CameraXBasic: currentDest?: androidx.navigation.fragment.FragmentNavigator Destination@b713195美元

我怀疑这个故事的寓意,至少在这个时候,是要非常小心地混合导航和fragmentManager导航。

更新到@AlexNuts回答以支持导航到嵌套图。当一个动作使用一个嵌套图作为目的地时,如下所示:

<action
    android:id="@+id/action_foo"
    android:destination="@id/nested_graph"/>

此操作的目的ID不能与当前目的进行比较,因为当前目的不能是图形。必须解析嵌套图的起始目的地。

fun NavController.navigateSafe(directions: NavDirections) {
    // Get action by ID. If action doesn't exist on current node, return.
    val action = (currentDestination ?: graph).getAction(directions.actionId) ?: return
    var destId = action.destinationId
    val dest = graph.findNode(destId)
    if (dest is NavGraph) {
        // Action destination is a nested graph, which isn't a real destination.
        // The real destination is the start destination of that graph so resolve it.
        destId = dest.startDestination
    }
    if (currentDestination?.id != destId) {
        navigate(directions)
    }
}

然而,这将防止导航到同一目的地两次,这是有时需要的。为了允许这一点,你可以添加一个检查action.navOptions?. shouldlaunchsingletop(),并添加app:launchSingleTop="true"的动作,你不希望重复的目的地。

在思考了Ian Lake在推特上的建议后,我想出了以下方法。将NavControllerWrapper定义如下:

class NavControllerWrapper constructor(
  private val navController: NavController
) {

  fun navigate(
    @IdRes from: Int,
    @IdRes to: Int
  ) = navigate(
    from = from,
    to = to,
    bundle = null
  )

  fun navigate(
    @IdRes from: Int,
    @IdRes to: Int,
    bundle: Bundle?
  ) = navigate(
    from = from,
    to = to,
    bundle = bundle,
    navOptions = null,
    navigatorExtras = null
  )

  fun navigate(
    @IdRes from: Int,
    @IdRes to: Int,
    bundle: Bundle?,
    navOptions: NavOptions?,
    navigatorExtras: Navigator.Extras?
  ) {
    if (navController.currentDestination?.id == from) {
      navController.navigate(
        to,
        bundle,
        navOptions,
        navigatorExtras
      )
    }
  }

  fun navigate(
    @IdRes from: Int,
    directions: NavDirections
  ) {
    if (navController.currentDestination?.id == from) {
      navController.navigate(directions)
    }
  }

  fun navigateUp() = navController.navigateUp()

  fun popBackStack() = navController.popBackStack()
}

然后在导航代码中:

val navController = navControllerProvider.getNavController()
navController.navigate(from = R.id.main, to = R.id.action_to_detail)

如果你有的话,这种情况也会发生 一个片段a和片段B的ViewPager 你试着从B导航到C

因为在ViewPager中片段不是a的目的地,所以你的图形不会知道你在B上。

一种解决方案是在B中使用A方向导航到C

I caught this exception after some renames of classes. For example: I had classes called FragmentA with @+is/fragment_a in navigation graph and FragmentB with @+id/fragment_b. Then I deleted FragmentA and renamed FragmentB to FragmentA. So after that node of FragmentA still stayed in navigation graph, and android:name of FragmentB's node was renamed path.to.FragmentA. I had two nodes with the same android:name and different android:id, and the action I needed were defined on node of removed class.