参数异常:导航目标 xxx 在此 NavController 中未知。

226

我在使用新的Android Navigation Architecture组件时遇到了问题,当我尝试从一个片段导航到另一个时,我会收到这个奇怪的错误:

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

除了这个特定的导航之外,其他导航都正常工作。

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

将不胜感激您的任何帮助。


请提供一些代码以便更好地理解。 - Alex
到目前为止,随着库的新版本的发布,这个bug的出现率已经降低了,但我认为这个库的文档还不够完善。 - Jerry Okafor
40个回答

3

同一个快速点击导航崩溃问题的另一种解决方案:

fun NavController.doIfCurrentDestination(@IdRes destination: Int, action: NavController.()-> Unit){
    if(this.currentDestination?.id == destination){action()}
}

然后像这样使用:

findNavController().doIfCurrentDestination(R.id.my_destination){ navigate(...) }

这种解决方案的好处是,您可以轻松地包装任何现有的对 naviagte() 的调用,并使用您已经使用的任何签名,无需制作数百个重载版本。

3
我通过检查下一个动作是否存在于当前目的地来解决这个问题。
public static void launchFragment(BaseFragment fragment, int action) {
    if (fragment != null && NavHostFragment.findNavController(fragment).getCurrentDestination().getAction(action) != null) {       
        NavHostFragment.findNavController(fragment).navigate(action);
    }
}

public static void launchFragment(BaseFragment fragment, NavDirections directions) {
    if (fragment != null && NavHostFragment.findNavController(fragment).getCurrentDestination().getAction(directions.getActionId()) != null) {       
        NavHostFragment.findNavController(fragment).navigate(directions);
    }
}

这可以解决用户快速点击两个不同按钮时的问题。

3
一种荒谬但非常强大的方法是: 只需调用此函数:
view?.findNavController()?.navigateSafe(action)

只需创建此扩展:

fun NavController.navigateSafe(
    navDirections: NavDirections? = null
) {
    try {
        navDirections?.let {
            this.navigate(navDirections)
        }
    }
    catch (e:Exception)
    {
        e.printStackTrace()
    }
}

谢谢您提供这个简单易懂但功能强大的示例。无需烦恼于点击监听器,我们仍然可以使用所有的API =D - baskInEminence

2

我提供一个解决方案,它可以优雅地处理两种情况(双击、同时点击两个按钮),但尽可能不掩盖真正的错误。

我们可以使用一个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)
}

2
这个问题可能有很多原因。在我的情况下,我正在使用MVVM模型,并观察一个布尔值以进行导航,当布尔值为true时进行导航,否则不进行任何操作,这一切都很好,但是这里有一个错误。从目标片段按返回按钮时,我遇到了相同的问题。问题是布尔对象,因为我忘记将布尔值更改为false,这就造成了混乱。我只是在viewModel中创建了一个函数来更改它的值为false,并在findNavController()之后调用它即可。

2

我因在代码中同时使用导航抽屉和getSupportFragmentManager().beginTransaction().replace( )而遇到了相同的错误。

通过使用以下条件(测试目标),我摆脱了这个错误:

if (Navigation.findNavController(v).getCurrentDestination().getId() == R.id.your_destination_fragment_id)
Navigation.findNavController(v).navigate(R.id.your_action);

在我的情况下,之前的错误是在我点击导航抽屉选项时触发的。基本上,上面的代码隐藏了错误,因为在我的代码中,我在某个地方使用了getSupportFragmentManager().beginTransaction().replace()进行导航。条件为-
 if (Navigation.findNavController(v).getCurrentDestination().getId() ==
  R.id.your_destination_fragment_id) 

因为(Navigation.findNavController(v).getCurrentDestination().getId()一直指向主页片段,所以从未到达。您必须仅使用Navigation.findNavController(v).navigate(R.id.your_action)或nav graph控制器功能来执行所有导航操作。


1
在我的情况下,我通过验证所有导航操作是否正确管理到相应的图形,并在我的主活动文件中更新设备返回按键的代码(如下所示)来解决了这个问题:
 onBackPressedDispatcher.addCallback(this /* lifecycle owner */, object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
            // Back is pressed... Finishing the activity
            if (navHostFragment.childFragmentManager.backStackEntryCount == 0) {
                // First fragment is open, backstack is empty
               finish()
            } else {
                navHostFragment.navController.popBackStack()
            }
        }
    })

1
我在对一些类进行重命名后捕获了这个异常。例如: 我曾经有一个叫做FragmentA的类,在导航图中有一个@+id/fragment_a,还有一个叫做FragmentB的类,有一个@+id/fragment_b。然后我删除了FragmentA并将FragmentB重命名为FragmentA。因此,FragmentA的节点仍然留在导航图中,而FragmentB的节点的android:name被重命名为path.to.FragmentA。我有两个具有相同android:name但不同android:id的节点,并且我所需要的操作是在已删除类的节点上定义的。

1
当我按两次后退按钮时,这个问题就发生了。起初,我拦截了KeyListener并重写了KeyEvent.KEYCODE_BACK。我在名为OnResume的碎片函数中添加了下面的代码,然后这个问题解决了。
  override fun onResume() {
        super.onResume()
        view?.isFocusableInTouchMode = true
        view?.requestFocus()
        view?.setOnKeyListener { v, keyCode, event ->
            if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_BACK) {
                activity!!.finish()
                true
            }
            false
        }
    }

当它第二次发生,并且状态与第一次相同时,我发现我可能会使用 adsurd 函数。让我们分析一下这些情况。
  1. 首先,FragmentA 导航到 FragmentB,然后 FragmentB 导航到 FragmentA,然后按返回按钮... 崩溃出现。

  2. 其次,FragmentA 导航到 FragmentB,然后 FragmentB 导航到 FragmentC,FragmentC 导航到 FragmentA,然后按返回按钮... 崩溃出现。

所以我认为当按下返回按钮时,FragmentA 将返回到 FragmentB 或 FragmentC,然后导致登录混乱。最后,我发现名为 popBackStack 的函数可以用于后退而不是导航。
  NavHostFragment.findNavController(this@TeacherCloudResourcesFragment).
                        .popBackStack(
                            R.id.teacher_prepare_lesson_main_fragment,false
                        )

到目前为止,问题确实得到了解决。

1

我正在调用2.3.1导航,当应用程序配置更改时出现相同的错误。通过调试找到问题原因时,NavHostFragment中的GaphId没有生效,因为当前通过调用navController.setGraph()设置了ID。 NavHostFragmentGraphId只能从<androidx.fragment.app.FragmentContainerView/>标签中获取。此时,如果您的代码中动态设置了多个GraphId,则会出现此问题。当恢复界面时,在缓存的GraphId中找不到目标。在切换图形时,可以通过反射手动指定NavHostFragmentmGraphId的值来解决此问题。

navController.setGraph(R.navigation.home_book_navigation);
try {
    Field graphIdField = hostFragment.getClass().getDeclaredField("mGraphId");
    graphIdField.setAccessible(true);
    graphIdField.set(navHostFragment, R.navigation.home_book_navigation);
} catch (NoSuchFieldException | IllegalAccessException e) {
    e.printStackTrace();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接