IllegalArgumentException:此 NavController 未知导航目的地 xxx

Posted

技术标签:

【中文标题】IllegalArgumentException:此 NavController 未知导航目的地 xxx【英文标题】:IllegalArgumentException: navigation destination xxx is unknown to this NavController 【发布时间】:2018-12-06 05:31:34 【问题描述】:

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

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

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

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

任何帮助将不胜感激。

【问题讨论】:

请提供一些代码以便更好地理解。 到目前为止,该错误的发生率已经随着库的更新版本而降低,但我认为该库还没有很好的文档记录。 【参考方案1】:

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

您可以在此处阅读有关防止这种情况的更多信息:Android Preventing Double Click On A Button

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

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

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

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


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

HomeFragment.kt

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

    settingsButton.setOnSingleClickListener 
        // navigation call here
    

【讨论】:

关于使用2个手指同时点击2个视图的编辑!这对我来说是关键,并帮助我轻松地复制了这个问题。很好地更新了这些信息。 在调试阶段我碰巧点击了应用程序被卡住等待继续执行。似乎是另一个连续两次单击 IDE 的情况 谢谢。救了我几次崩溃和一些头疼:) 这个解决方案是为了解决真正的问题:导航组件。它也容易在速度较慢的设备上失败。创建和扩充一个新片段肯定会花费超过 200 毫秒。在该延迟之后,可以在显示片段之前发送第二次点击事件,我们又回到了同样的问题。 在我的应用程序中并没有真正起作用。至少在调试版本中,我仍然可以足够快地单击以产生崩溃。尽管实际上应该只有一个 ui 线程,但看起来像是线程安全的问题。奇怪。【参考方案2】:

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

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

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

【讨论】:

我有一个搜索应用程序,它使用带有参数的操作进行导航。因此它可以从 currentDestination 导航到它自己。我最终做了同样的事情,除了 navController.currentDestination == navController.graph.node 。不过感觉有点脏,我觉得我不应该这样做。 图书馆不应该强迫我们做这个检查,这确实很可笑。 我遇到了同样的问题。我有一个 EditText 和一个“保存”按钮,用于将 EditText 的内容存储在数据库中。它总是在按下“保存”按钮时崩溃。我怀疑原因与这样一个事实有关,为了能够按下“保存”按钮,我需要通过点击返回按钮来摆脱屏幕键盘。 这会检查错误情况,但不能解决问题。有趣的是,如果导航返回堆栈由于不必要的原因变为空,则此条件为真。 即使在 ios 中,有时当您多次按下按钮时也会推送多个 ViewController。估计安卓和iOS都有这个问题。【参考方案3】:

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

更新 为安全导航添加了全局操作的使用。

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)
    

【讨论】:

此解决方案不适用于在currentDestination 的操作列表之外定义的任何操作。假设您定义了一个全局操作并使用该操作进行导航。这将失败,因为该操作未在 currentDestination 的 列表中定义。添加像 currentDestination?.getAction(resId) != null || currentDestination?.id != resId 这样的检查应该可以解决它,但也可能无法涵盖所有​​情况。 @wchristiansen,谢谢你的笔记。我已经使用全局操作更新了代码 @AlexNuts 很好的答案。我认为您可以删除 ?: graph.getAction(resId) -> currentDestination?.getAction(resId) 将为全局或非全局操作返回一个操作(我已经测试过了)。此外,如果您使用 Safe Args 会更好 -> 而不是分别传入 navDirections: NavDirections 而不是 resIdargs @AlexNuts 注意此解决方案不支持导航到与当前目的地相同的目的地。 I.o.w.无法从带有 Bundle Y 的目的地 X 导航到带有 Bundle Z 的目的地 X。 感谢您的建议!但从我的角度来看,在 navigateSafe 包装器中处理异常要容易得多。我最终得到了以下解决方案:vadzimv.dev/2021/07/26/…【参考方案4】:

我为防止崩溃所做的事情如下:

我有一个 BaseFragment,我在其中添加了这个 fun,以确保 currentDestination 知道 destination

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

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

【讨论】:

这应该是公认的答案。接受的答案不支持导航到对话框 我认为这是最好的答案,谢谢【参考方案5】:

如果您有 具有片段 B 的 ViewPager 的片段 A 然后你尝试从 B 导航到 C

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

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

【讨论】:

在这种情况下,崩溃并不是每次都发生,而是很少发生。如何解决? 您可以在 navGraph 中添加全局操作并使用它进行导航 由于 B 不需要知道其确切的父级,因此最好通过 (parentFragment as? XActionListener)?.Xaction() 之类的接口使用 ADirections 并注意您可以将此函数作为局部变量保存,如果它是有帮助 您能否分享一个示例代码来说明这一点,因为我有同样的问题 任何人都可以提供示例代码,我遇到了同样的问题。有一个片段,然后是一个标签片段【参考方案6】:

在我的例子中,我使用自定义后退按钮进行向上导航。我调用了onBackPressed() 而不是下面的代码

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

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

【讨论】:

我不明白 onBackPressed 和 this 有什么区别,仍然卡在系统后退按钮上并覆盖它并用 this 替换似乎很疯狂 我同意这看起来很疯狂。我在android导航架构组件中遇到的很多东西都感觉有点疯狂,IMO设置得太死板了。考虑为我们的项目做我自己的实现,因为它只会造成太多的麻烦 对我不起作用...仍然出现同样的错误。【参考方案7】:

试试看

    创建此扩展函数(或普通函数):

已更新(无反射且更具可读性)

import androidx.fragment.app.Fragment
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import androidx.navigation.fragment.DialogFragmentNavigator
import androidx.navigation.fragment.FragmentNavigator

fun Fragment.safeNavigateFromNavController(directions: NavDirections) 
    val navController = findNavController()
    when (val destination = navController.currentDestination) 
        is FragmentNavigator.Destination -> 
            if (javaClass.name == destination.className) 
                navController.navigate(directions)
            
        
        is DialogFragmentNavigator.Destination -> 
            if (javaClass.name == destination.className) 
                navController.navigate(directions)
            
        
    

OLD(带反射)

import androidx.fragment.app.Fragment
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import androidx.navigation.fragment.FragmentNavigator

inline fun <reified T : Fragment> NavController.safeNavigate(directions: NavDirections) 
    val destination = this.currentDestination as FragmentNavigator.Destination
    if (T::class.java.name == destination.className) 
        navigate(directions)
    

    并从您的 Fragment 中像这样使用:
val direction = FragmentOneDirections.actionFragmentOneToFragmentTwo()
// new usage
safeNavigateFromNavController(direction)

// old usage
// findNavController().safeNavigate<FragmentOne>(action)

我的问题是

我有一个片段 (FragmentOne),它连接到另外两个片段(FragmentTwo 和 FragmentThree)。在某些低端设备中,用户按下重定向到 FragmentTwo 的按钮,但在用户按下重定向到 FragmentThree 的按钮后几毫秒。结果是:

致命异常:java.lang.IllegalArgumentException 动作/目的地 action_fragmentOne_to_fragmentTwo 找不到 从当前目的地 Destination(fragmentThree) class=片段三

我的解决办法是:

我检查当前目的地是否属于当前片段。如果为真,我执行导航动作。

就是这样!

【讨论】:

@AlexShevchyshen 我发布了没有反思且更具可读性的更新。 +1 但您必须在此扩展函数中传递 navDirections 对象,而不是动作 safeNavigateFromNavController(navDirection)【参考方案8】:

TL;DRtry-catch 包裹您的navigate 呼叫(简单方法),或者确保在短时间内只有一个navigate 呼叫。这个问题很可能不会消失。在您的应用中复制更大的代码 sn-p 并试用。

你好。根据上面的几个有用的回答,我想分享我的可以扩展的解决方案。

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

@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);

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

    第一次 navigate 调用总是可以正常工作; navigate 方法的第二次和所有其他调用在 IllegalArgumentException 中解析。

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

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.");
    

因此上面的代码只改变了一行:

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

到这里:

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

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

欢迎提出任何想法!

究竟是什么导致了崩溃

请记住,当我们使用方法 Navigation.findNavController 时,我们使用相同的导航图、导航控制器和返回堆栈。

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

这个确切的错误可以在NavController.java方法findDestination中找到。

【讨论】:

【参考方案9】:

就我而言,当我在viewpager 片段中重新使用我的片段之一作为viewpager 的子片段时,就会出现问题。 在 Navigation xml 中添加了 viewpager Fragment(即父 Fragment),但未在 viewpager 父 Fragment 中添加操作。

nav.xml
//reused fragment
<fragment
    android:id="@+id/navigation_to"
    android:name="com.package.to_Fragment"
    android:label="To Frag"
    tools:layout="@layout/fragment_to" >
    //issue got fixed when i added this action to the viewpager parent also
    <action android:id="@+id/action_to_to_viewall"
        app:destination="@+id/toViewAll"/>
</fragment>
....
// viewpager parent fragment
<fragment
    android:id="@+id/toViewAll"
    android:name="com.package.ViewAllFragment"
    android:label="to_viewall_fragment"
    tools:layout="@layout/fragment_view_all">

通过将操作添加到父 viewpager 片段来修复问题,如下所示:

nav.xml
//reused fragment
<fragment
    android:id="@+id/navigation_to"
    android:name="com.package.to_Fragment"
    android:label="To Frag"
    tools:layout="@layout/fragment_to" >
    //issue got fixed when i added this action to the viewpager parent also
    <action android:id="@+id/action_to_to_viewall"
        app:destination="@+id/toViewAll"/>
</fragment>
....
// viewpager parent fragment
<fragment
    android:id="@+id/toViewAll"
    android:name="com.package.ViewAllFragment"
    android:label="to_viewall_fragment"
    tools:layout="@layout/fragment_view_all"/>
    <action android:id="@+id/action_to_to_viewall"
        app:destination="@+id/toViewAll"/>
</fragment>

【讨论】:

【参考方案10】:

今天

def navigationVersion = "2.2.1"

问题仍然存在。我对 Kotlin 的处理方法是:

// To avoid "java.lang.IllegalArgumentException: navigation destination is unknown to this NavController", se more https://***.com/q/51060762/6352712
fun NavController.navigateSafe(
    @IdRes destinationId: Int,
    navDirection: NavDirections,
    callBeforeNavigate: () -> Unit
) 
    if (currentDestination?.id == destinationId) 
        callBeforeNavigate()
        navigate(navDirection)
    


fun NavController.navigateSafe(@IdRes destinationId: Int, navDirection: NavDirections) 
    if (currentDestination?.id == destinationId) 
        navigate(navDirection)
    

【讨论】:

【参考方案11】:

在我的情况下,我有多个导航图文件,我试图从 1 个导航图位置移动到另一个导航图中的目的地。

为此,我们必须像这样在第一个导航图中包含第二个导航图

<include app:graph="@navigation/included_graph" />

并将其添加到您的操作中:

<action
        android:id="@+id/action_fragment_to_second_graph"
        app:destination="@id/second_graph" />

second_graph 在哪里:

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/second_graph"
    app:startDestination="@id/includedStart">

在第二张图中。

更多信息here

【讨论】:

【参考方案12】:

您可以在导航前检查请求导航的Fragment是否仍然是当前目的地taken from this gist。

它基本上在片段上设置了一个标签以供以后查找。

/**
 * Returns true if the navigation controller is still pointing at 'this' fragment, or false if it already navigated away.
 */
fun Fragment.mayNavigate(): Boolean 

    val navController = findNavController()
    val destinationIdInNavController = navController.currentDestination?.id
    val destinationIdOfThisFragment = view?.getTag(R.id.tag_navigation_destination_id) ?: destinationIdInNavController

    // check that the navigation graph is still in 'this' fragment, if not then the app already navigated:
    if (destinationIdInNavController == destinationIdOfThisFragment) 
        view?.setTag(R.id.tag_navigation_destination_id, destinationIdOfThisFragment)
        return true
     else 
        Log.d("FragmentExtensions", "May not navigate: current destination is not the current fragment.")
        return false
    

R.id.tag_navigation_destination_id 只是一个您必须添加到您的 ids.xml 的 ID,以确保它是唯一的。 &lt;item name="tag_navigation_destination_id" type="id" /&gt;

有关错误和解决方案的更多信息,以及"Fixing the dreaded “… is unknown to this NavController” 中的navigateSafe(...) 扩展方法

【讨论】:

我研究了几个不同的解决方案来解决这个问题,你的绝对是最好的。看到这么少的爱让我很难过 用类似***.com/a/15021758/1572848的东西创建一个唯一标识符来代替NAV_DESTINATION_ID可能会很有用 标签来自哪里,为什么需要它?我遇到的问题是导航组件上的实际 ID 与 R.id 中的这些不匹配。 R.id.tag_navigation_destination_id 只是一个您必须添加到 ids.xml 的 ID,以确保它是唯一的。 &lt;item name="tag_navigation_destination_id" type="id" /&gt; 即使使用这种解决方案,在弹出回栈时仍有可能发生原始崩溃。不妨加fun Fragment.popBackStackSafe() if (mayNavigate()) findNavController().popBackStack() 【参考方案13】:

我已经解决了同样的问题,方法是在导航之前检查而不是样板代码来点击即时控制

 if (findNavController().currentDestination?.id == R.id.currentFragment) 
        findNavController().navigate(R.id.action_current_next)
/* Here R.id.currentFragment is the id of current fragment in navigation graph */

根据这个答案

https://***.com/a/56168225/7055259

【讨论】:

【参考方案14】:

在我的情况下,出现错误是因为我在启动屏幕后启用了 Single TopClear Task 选项的导航操作。

【讨论】:

但是 clearTask 已被弃用,你应该使用 popUpTo() 代替。 @Po10cio 这些标志都不需要,我只是将其删除并已修复。【参考方案15】:

在考虑了this twitter thread 中 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)

【讨论】:

Kotlin 具有完全用于此目的的扩展函数,无需包装器。 当你在这里和那里使用扩展函数时,你不能执行单元测试。或者,当您使用包装器时,您会在组件中引入接缝,因此您可以根据需要模拟组件并执行纯单元测试。【参考方案16】:

我通过检查当前目的地中是否存在下一个操作来解决此问题

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

如果用户快速点击 2 个不同的按钮,这会解决问题

【讨论】:

【参考方案17】:

正如其他答案中提到的,此异常通常发生在用户时

    同时点击多个处理导航的视图 多次点击处理导航的视图。

使用计时器禁用点击不是处理此问题的合适方法。如果在计时器到期后用户还没有导航到目的地,那么应用程序无论如何都会崩溃,并且在许多情况下,导航不是要执行的操作,因此需要快速单击。

在情况 1 中,xml 中的 android:splitMotionEvents="false" 或源文件中的 setMotionEventSplittingEnabled(false) 应该会有所帮助。将此属性设置为 false 将只允许一个视图进行点击。你可以阅读它here

在情况 2 中,会出现一些延迟导航过程的情况,允许用户多次单击视图(API 调用、动画等)。如果可能,应该解决根本问题,以便立即进行导航,不允许用户单击视图两次。如果延迟不可避免,例如在 API 调用的情况下,禁用视图或使其不可点击将是合适的解决方案。

【讨论】:

【参考方案18】:

为了避免这种崩溃,我的一位同事编写了一个小库,它公开了一个 SafeNavController,一个围绕 NavController 的包装器,并处理由于多个导航命令同时发生这种崩溃的情况。

这是关于整个问题和解决方案的简短article。

你可以找到图书馆here。

【讨论】:

【参考方案19】:

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

例如,原始的 CameraX 基本示例使用了 fragmentManager 的 backstack 导航,如下所示,它看起来好像没有正确地与 Navigation 交互:

// 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

之后:D/CameraXBasic: currentDest?: androidx.navigation.fragment.FragmentNavigator$Destination@9807d8f

CameraX基础样例更新版使用Navigation返回如下:

 // 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

之后:D/CameraXBasic: currentDest?: androidx.navigation.fragment.FragmentNavigator$Destination@b713195

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

【讨论】:

这听起来很合理,我会进一步调查。有没有人能够验证或证实这一说法? @JerryOkafor - 我已经在我正在开发的基于 CameraX Sample 的应用程序中对其进行了测试并对其进行了验证,但如果其他人也看到了这一点会很好。实际上,我在同一个应用程序的一个地方错过了“后退导航”,所以最近又修复了它。【参考方案20】:

一个荒谬但非常强大的方法是: 简单地称之为:

view?.findNavController()?.navigateSafe(action)

只需创建此扩展:

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

【讨论】:

感谢您提供这个简单易懂但功能强大的示例。无需使用点击监听器,我们仍然可以使用所有 API =D【参考方案21】:

这个问题可能有很多原因。 就我而言,我使用的是 MVVM 模型,并且我正在观察一个用于导航的布尔值 当布尔值为真时-> 导航 否则什么都不做 这工作正常,但这里有一个错误

当从目标片段按下返回按钮时,我遇到了同样的问题。问题是布尔对象,因为我忘记将布尔值更改为 false,这造成了混乱。我只是在 viewModel 中创建了一个函数来更改它的值为 false 并在 findNavController() 之后调用它

【讨论】:

【参考方案22】:

我写了这个扩展

fun Fragment.navigateAction(action: NavDirections) 
    val navController = this.findNavController()
    if (navController.currentDestination?.getAction(action.actionId) == null) 
        return
     else 
        navController.navigate(action)
    

【讨论】:

【参考方案23】:

我遇到了同样的错误,因为我在代码的某处同时使用了 Navigation Drawer 和 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) 或导航图控制器函数。

【讨论】:

【参考方案24】:

更新到@AlexNuts answer 以支持导航到嵌套图。当一个动作使用嵌套图作为目标时:

<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" 添加到您不希望重复目标的操作。

【讨论】:

【参考方案25】:

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

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

【讨论】:

【参考方案26】:

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

【讨论】:

【参考方案27】:

当我按两次后退按钮时,我会出现这种情况。首先,我拦截KeyListener 并覆盖KeyEvent.KEYCODE_BACK。我在 Fragment 的 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函数。让我们分析一下这些情况。

    首先,FragmentA 导航到 FragmentB ,然后 FragmentB 导航到 FragmentA,然后按返回按钮...出现崩溃。

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

所以我认为当按下返回按钮时,FragmentA 会返回到 FragmentB 或 FragmentC,然后导致登录混乱。最后我发现popBackStack这个函数可以用于返回而不是导航。

  NavHostFragment.findNavController(this@TeacherCloudResourcesFragment).
                        .popBackStack(
                            R.id.teacher_prepare_lesson_main_fragment,false
                        )

至此,问题真的解决了。

【讨论】:

【参考方案28】:

通常当这种情况发生在我身上时,我遇到了 Charles Madere 描述的问题:在同一个 ui 上触发了两个导航事件,一个更改了 currentDestination,另一个失败了,因为 currentDestination 已更改。 如果您双击或单击两个视图,单击侦听器调用 findNavController.navigate,就会发生这种情况。

因此,要解决此问题,您可以使用 if-checks、try-catch 或者如果您有兴趣,可以使用 findSafeNavController() 在导航之前为您进行检查。它还有一个 lint-check 以确保您不会忘记这个问题。

GitHub

Article detailing the issue

【讨论】:

【参考方案29】:

如果点击太快,会导致null和crash。

我们可以使用 RxBinding 库来帮助解决这个问题。您可以在点击发生之前添加限制和持续时间。

 RxView.clicks(view).throttleFirst(duration, TimeUnit.MILLISECONDS)
            .subscribe(__ -> 
            );

这些articles 在 Android 上进行节流可能会有所帮助。干杯!

【讨论】:

【参考方案30】:

我正在调用 2.3.1 Navigation,并且在应用程序配置更改时出现相同的错误。通过Debug找到问题原因后,NavHostFragment中的GaphId没有生效,因为当前调用navController.setGraph()设置的ID。 NavHostFragmentGraphId 只能从&lt;androidx.fragment.app.FragmentContainerView/&gt; 标签中获取。这时候如果你的代码中动态设置了多个GraphIds就会出现这个问题。接口恢复后,在缓存的GraphId中找不到Destination。可以通过在切换Graph时通过反射手动指定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();

【讨论】:

以上是关于IllegalArgumentException:此 NavController 未知导航目的地 xxx的主要内容,如果未能解决你的问题,请参考以下文章

IllegalArgumentException:无效的列纬度

Retrofit-IllegalArgumentException:意外的 url

引起:java.lang.IllegalArgumentException:属性'driverClassName'不能为空

IllegalArgumentException:接收方未注册

IllegalArgumentException 介绍

java.lang.IllegalArgumentException:基本 URI 不能为空