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
可能会有所帮助。
例如,如果您在导航图fragmentA
和fragmentB
上有两个片段目的地,并且从fragmentA
到fragmentB
只有一个动作。当您已经在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
而不是 resId
和 args
。
@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;DR 用try-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,以确保它是唯一的。 <item name="tag_navigation_destination_id" type="id" />
有关错误和解决方案的更多信息,以及"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,以确保它是唯一的。 <item name="tag_navigation_destination_id" type="id" />
即使使用这种解决方案,在弹出回栈时仍有可能发生原始崩溃。不妨加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 Top
和 Clear 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。 NavHostFragment
的GraphId
只能从<androidx.fragment.app.FragmentContainerView/>
标签中获取。这时候如果你的代码中动态设置了多个GraphId
s就会出现这个问题。接口恢复后,在缓存的GraphId
中找不到Destination。可以通过在切换Graph时通过反射手动指定NavHostFragment
中mGraphId
的值来解决这个问题。
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'不能为空