Java IllegalArgumentException: 导航目标 xxx 对该 NavController 未知

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/51060762/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 23:55:48  来源:igfitidea点击:

IllegalArgumentException: navigation destination xxx is unknown to this NavController

javaandroidkotlinandroid-navigationandroid-architecture-navigation

提问by Jerry Okafor

I am having issue with the new Android Navigation Architecture component when I try to navigate from one Fragment to another, I get this weird error:

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

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

Every other navigation works fine except this particular one.

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

I use findNavController()function of Fragment to get access to the NavController.

我使用findNavController()Fragment 的功能来访问NavController.

Any help will be appreciated.

任何帮助将不胜感激。

回答by Eury Pérez Beltré

In my case the bug ocurred because I had a navigation action with the Single Topand the Clear Taskoptions enabled after a splash screen.

在我的情况下,错误发生是因为我有一个导航操作,Single Top并且在Clear Task启动屏幕后启用了选项。

回答by Patrick

It seems like you are clearing task. An app might have a one-time setup or series of login screens. These conditional screens should not be considered the starting destination of your app.

看起来你正在清理任务。应用程序可能有一次性设置或一系列登录屏幕。这些条件屏幕不应被视为您的应用程序的起始目的地。

https://developer.android.com/topic/libraries/architecture/navigation/navigation-conditional

https://developer.android.com/topic/libraries/architecture/navigation/navigation-conditional

回答by Neil

In my case I was using a custom back button for navigating up. I called onBackPressed()in stead of the following code

就我而言,我使用自定义后退按钮进行导航。我调用onBackPressed()而不是以下代码

findNavController(R.id.navigation_host_fragment).navigateUp()

This caused the IllegalArgumentExceptionto occur. After I changed it to use the navigateUp()method in stead, I didn't have a crash again.

这导致了IllegalArgumentException发生。在我将其更改为使用该navigateUp()方法后,我没有再次崩溃。

回答by Charles Madere

In my case, if the user clicks the same view twice very very quickly, this crash will occur. So you need to implement some sort of logic to prevent multiple quick clicks... Which is very annoying, but it appears to be necessary.

就我而言,如果用户非常快速地两次单击同一个视图,就会发生这种崩溃。所以你需要实现某种逻辑来防止多次快速点击......这很烦人,但似乎是必要的。

You can read up more on preventing this here: Android Preventing Double Click On A Button

您可以在此处阅读有关防止这种情况的更多信息:Android 防止双击按钮

Edit 3/19/2019: Just to clarify a bit further, this crash is not exclusively reproducible by just "clicking the same view twice very very quickly". Alternatively, you can just use two fingers and click two (or more) views at the same time, where each view has their own navigation that they would perform. This is especiallyeasy to do when you have a list of items. The above info on multiple click prevention will handle this case.

2019 年 3 月 19 日编辑:为了进一步澄清,这种崩溃不能仅通过“非常快速地单击同一视图两次”来完全重现。或者,您可以只用两根手指同时单击两个(或更多)视图,其中每个视图都有自己的导航供它们执行。当您有一个项目列表时,这尤其容易做到。以上关于多次点击预防的信息将处理这种情况。

Edit 4/16/2020: Just in case you're not terribly interested in reading through that Stack Overflow post above, I'm including my own (Kotlin) solution that I've been using for a long time now.

2020 年 4 月 16 日编辑:以防万一您对阅读上面的 Stack Overflow 帖子不太感兴趣,我将包含我自己的 (Kotlin) 解决方案,我已经使用了很长时间了。

OnSingleClickListener.kt

OnSingleClickListener.kt

class OnSingleClickListener : View.OnClickListener {

    private val onClickListener: View.OnClickListener

    constructor(listener: View.OnClickListener) {
        onClickListener = listener
    }

    constructor(listener: (View) -> Unit) {
        onClickListener = View.OnClickListener { listener.invoke(it) }
    }

    override fun onClick(v: View) {
        val currentTimeMillis = System.currentTimeMillis()

        if (currentTimeMillis >= previousClickTimeMillis + DELAY_MILLIS) {
            previousClickTimeMillis = currentTimeMillis
            onClickListener.onClick(v)
        }
    }

    companion object {
        // Tweak this value as you see fit. In my personal testing this
        // seems to be good, but you may want to try on some different
        // devices and make sure you can't produce any crashes.
        private const val DELAY_MILLIS = 200L

        private var previousClickTimeMillis = 0L
    }

}

ViewExt.kt

ViewExt.kt

fun View.setOnSingleClickListener(l: View.OnClickListener) {
    setOnClickListener(OnSingleClickListener(l))
}

fun View.setOnSingleClickListener(l: (View) -> Unit) {
    setOnClickListener(OnSingleClickListener(l))
}

HomeFragment.kt

HomeFragment.kt

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    settingsButton.setOnSingleClickListener {
        // navigation call here
    }
}

回答by theJian

Check currentDestinationbefore calling navigate might be helpful.

currentDestination在调用导航之前检查可能会有所帮助。

For example, if you have two fragment destinations on the navigation graph fragmentAand fragmentB, and there is only one action from fragmentAto fragmentB. calling navigate(R.id.action_fragmentA_to_fragmentB)will result in IllegalArgumentExceptionwhen you were already on fragmentB. Therefor you should always check the currentDestinationbefore navigating.

例如,如果导航图fragmentA和上有两个片段目的地fragmentB,并且只有一个动作从fragmentAfragmentB。调用navigate(R.id.action_fragmentA_to_fragmentB)将导致IllegalArgumentException您已经在fragmentB. 因此,您应该始终currentDestination在导航前检查。

if (navController.currentDestination?.id == R.id.fragmentA) {
    navController.navigate(R.id.action_fragmentA_to_fragmentB)
}

回答by Alex Nuts

You can check requested action in current destination of navigation controller.

您可以在导航控制器的当前目的地检查请求的操作。

UPDATEadded usage of global actions for safe navigation.

UPDATE添加了使用全局操作以进行安全导航。

fun NavController.navigateSafe(
        @IdRes resId: Int,
        args: Bundle? = null,
        navOptions: NavOptions? = null,
        navExtras: Navigator.Extras? = null
) {
    val action = currentDestination?.getAction(resId) ?: graph.getAction(resId)
    if (action != null && currentDestination?.id != action.destinationId) {
        navigate(resId, args, navOptions, navExtras)
    }
}

回答by VasyaFromRussia

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

在对类进行了一些重命名后,我发现了这个异常。例如:我有类调用FragmentA@+is/fragment_a导航图和FragmentB使用@+id/fragment_b。然后我删除并重FragmentA命名FragmentBFragmentA. 所以在那之后FragmentA仍然留在导航图android:nameFragmentB的节点,并且的节点被重命名为path.to.FragmentA。我有两个相同android:name和不同的节点,android:id我需要的操作是在已删除类的节点上定义的。

回答by AntPachon

It could also happen if you have a Fragment A with a ViewPager of Fragments B And you try to navigate from B to C

如果您有一个带有 Fragment B 的 ViewPager 的 Fragment A 并且您尝试从 B 导航到 C,也可能发生这种情况

Since in the ViewPager the fragments are not a destination of A, your graph wouldn't know you are on B.

由于在 ViewPager 中,片段不是 A 的目的地,因此您的图表不会知道您在 B 上。

A solution can be to use ADirections in B to navigate to C

一个解决方案可以是在 B 中使用 ADirections 导航到 C

回答by Jenea Vranceanu

TL;DRWrap your navigatecalls with try-catch(simple way), or make sure there will be only one call of navigatein short period of time. This issue likely won't go away. Copy bigger code snippet in your app and try out.

TL;DRnavigatetry-catch(简单的方法)包裹你的电话,或者确保navigate在短时间内只有一个电话。这个问题可能不会消失。在您的应用中复制更大的代码片段并尝试。

Hello. Based on a couple of useful responses above, I would like to share my solution that can be extended.

你好。基于以上几个有用的回复,我想分享我可以扩展的解决方案。

Here is the code that caused this crash in my application:

这是在我的应用程序中导致此崩溃的代码:

@Override
public void onListItemClicked(ListItem item) {
    Bundle bundle = new Bundle();
    bundle.putParcelable(SomeFragment.LIST_KEY, item);
    Navigation.findNavController(recyclerView).navigate(R.id.action_listFragment_to_listItemInfoFragment, bundle);
}

A way to easily reproduce the bug is to tap with multiple fingers on the list of items where click on each item resolves in the navigation to the new screen (basically the same as people noted - two or more clicks in a very short period of time). I noticed that:

一种轻松重现错误的方法是用多个手指点击项目列表,点击每个项目会在导航到新屏幕中解析(与人们注意到的基本相同 - 在很短的时间内点击两次或多次)。我注意到:

  1. First navigateinvocation always works fine;
  2. Second and all other invocations of the navigatemethod resolve in IllegalArgumentException.
  1. 第一次navigate调用总是工作正常;
  2. 第二次和该navigate方法的所有其他调用都在IllegalArgumentException.

From my point of view, this situation may appear very often. Since the repeating of code is a bad practice and it is always good to have one point of influence I thought of the next solution:

在我看来,这种情况可能会经常出现。由于重复代码是一种不好的做法,并且有一点影响力总是好的,我想到了下一个解决方案:

public class NavigationHandler {

public static void navigate(View view, @IdRes int destination) {
    navigate(view, destination, /* args */null);
}

/**
 * Performs a navigation to given destination using {@link androidx.navigation.NavController}
 * found via {@param view}. Catches {@link IllegalArgumentException} that may occur due to
 * multiple invocations of {@link androidx.navigation.NavController#navigate} in short period of time.
 * The navigation must work as intended.
 *
 * @param view        the view to search from
 * @param destination destination id
 * @param args        arguments to pass to the destination
 */
public static void navigate(View view, @IdRes int destination, @Nullable Bundle args) {
    try {
        Navigation.findNavController(view).navigate(destination, args);
    } catch (IllegalArgumentException e) {
        Log.e(NavigationHandler.class.getSimpleName(), "Multiple navigation attempts handled.");
    }
}

}

}

And thus the code above changes only in one line from this:

因此,上面的代码仅在一行中更改:

Navigation.findNavController(recyclerView).navigate(R.id.action_listFragment_to_listItemInfoFragment, bundle);

to this:

对此:

NavigationHandler.navigate(recyclerView, R.id.action_listFragment_to_listItemInfoFragment, bundle);

It even became a little bit shorter. The code was tested in the exact place where the crash occurred. Did not experience it anymore, and will use the same solution for other navigations to avoid the same mistake further.

它甚至变得更短了一点。代码在崩溃发生的确切位置进行了测试。没有再体验过,其他导航会使用相同的解决方案,以避免进一步出现相同的错误。

Any thoughts are welcome!

欢迎任何想法!

What exactly causes the crash

究竟是什么导致了崩溃

Remember that here we work with the same navigation graph, navigation controller and back-stack when we use method Navigation.findNavController.

请记住,在这里,当我们使用 method 时,我们使用相同的导航图、导航控制器和后台堆栈Navigation.findNavController

We always get the same controller and graph here. When navigate(R.id.my_next_destination)is called graph and back-stack changes almost instantlywhile UI is not updated yet. Just not fast enough, but that is ok. After back-stack has changed the navigation system receives the second navigate(R.id.my_next_destination)call. Since back-stack has changed we now operate relative to the top fragment in the stack. The top fragment is the fragment you navigate to by using R.id.my_next_destination, but it does not contain next any further destinations with ID R.id.my_next_destination. Thus you get IllegalArgumentExceptionbecause of the ID that the fragment knows nothing about.

我们总是在这里得到相同的控制器和图形。当UI 尚未更新时,何时navigate(R.id.my_next_destination)调用图形和后台堆栈几乎会立即更改。只是不够快,但没关系。在 back-stack 改变后,导航系统接收第二个navigate(R.id.my_next_destination)调用。由于 back-stack 发生了变化,我们现在相对于堆栈中的顶部片段进行操作。顶部片段是您使用 导航到的片段R.id.my_next_destination,但它不包含接下来任何带有 ID 的其他目的地R.id.my_next_destination。因此,您得到的IllegalArgumentException是片段一无所知的 ID。

This exact error can be found in NavController.javamethod findDestination.

这个确切的错误可以在NavController.javamethod 中找到findDestination

回答by Douglas Kazumi

What I did to prevent the crash is the following:

我为防止崩溃所做的工作如下:

I have a BaseFragment, in there I've added this funto ensure that the destinationis known by the currentDestination:

我有一个 BaseFragment,在那里我添加了这个fun以确保它destination被以下的人知道currentDestination

fun navigate(destination: NavDirections) = with(findNavController()) {
    currentDestination?.getAction(destination.actionId)
        ?.let { navigate(destination) }
}

Worth noting that I'm using the SafeArgsplugin.

值得注意的是,我正在使用SafeArgs插件。