当我试图从一个片段导航到另一个片段时,我遇到了新的Android导航架构组件的问题,我得到了这个奇怪的错误:
java.lang.IllegalArgumentException: navigation destination XXX
is unknown to this NavController
其他导航都很好,除了这个。
我使用Fragment的findNavController()函数来访问NavController。
任何帮助都将不胜感激。
当我试图从一个片段导航到另一个片段时,我遇到了新的Android导航架构组件的问题,我得到了这个奇怪的错误:
java.lang.IllegalArgumentException: navigation destination XXX
is unknown to this NavController
其他导航都很好,除了这个。
我使用Fragment的findNavController()函数来访问NavController。
任何帮助都将不胜感激。
看来你在完成任务。应用程序可能有一次性设置或一系列登录屏幕。这些有条件的屏幕不应该被认为是应用程序的起始目的地。
https://developer.android.com/topic/libraries/architecture/navigation/navigation-conditional
在我的例子中,我使用了一个自定义的后退按钮来向上导航。我调用了onBackPressed()而不是下面的代码
findNavController(R.id.navigation_host_fragment).navigateUp()
这导致发生IllegalArgumentException。在我将其更改为使用navigateUp()方法之后,我就不会再次崩溃了。
在我的例子中,如果用户非常非常快地点击同一个视图两次,这个崩溃就会发生。所以你需要实现某种逻辑来防止多次快速点击……这很烦人,但似乎是必要的。
你可以在这里阅读更多关于防止这种情况的内容:Android防止双击按钮
编辑3/19/2019:为了进一步澄清一点,这个崩溃不是仅仅通过“非常非常快地单击同一个视图两次”就可以完全重现的。或者,您可以使用两个手指同时单击两个(或更多)视图,其中每个视图都有自己的导航。当你有一个项目列表时,这尤其容易做到。以上关于多次点击预防的信息将处理这种情况。
编辑4/16/2020:以防你对上面的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
}
}
在调用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)
}
您可以在导航控制器的当前目标中检查请求的操作。
更新 为安全导航增加了全局操作的使用。
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)
}
}
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.
如果你有的话,这种情况也会发生 一个片段a和片段B的ViewPager 你试着从B导航到C
因为在ViewPager中片段不是a的目的地,所以你的图形不会知道你在B上。
一种解决方案是在B中使用A方向导航到C
用try-catch(简单的方法)包装你的导航调用,或者确保在短时间内只有一个导航调用。这个问题可能不会消失。复制更大的代码片段在你的应用程序和尝试。
你好。基于上面的一些有用的回答,我想分享我的解决方案,可以扩展。
下面是导致我的应用程序崩溃的代码:
@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);
}
一个很容易重现这个错误的方法是用多个手指在项目列表上点击,点击每个项目就会在导航到新屏幕上解决(基本上和人们注意到的一样——在很短的时间内点击两次或两次以上)。我注意到:
第一次导航调用总是正常工作; 第二个和所有其他的导航方法调用在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时,我们使用相同的导航图、导航控制器和后堆栈。
We always get the same controller and graph here. When navigate(R.id.my_next_destination) is called graph and back-stack changes almost instantly while 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 IllegalArgumentException because of the ID that the fragment knows nothing about.
这个确切的错误可以在NavController.java方法findDestination中找到。
为了防止崩溃,我采取了如下措施:
我有一个BaseFragment,在那里我添加了这个乐趣,以确保目的地是已知的currentDestination:
fun navigate(destination: NavDirections) = with(findNavController()) {
currentDestination?.getAction(destination.actionId)
?.let { navigate(destination) }
}
值得注意的是,我正在使用SafeArgs插件。
这发生在我身上,我的问题是我在标签项片段上单击FAB。我试图从一个标签项片段导航到另一个片段。
但是根据Ian Lake的回答,我们必须使用tablayout和viewpager,没有导航组件支持。因此,没有从包含片段的标签布局到标签项片段的导航路径。
ex:
containing fragment -> tab layout fragment -> tab item fragment -> another fragment
解决方案是创建一个从包含片段的标签布局到预期片段的路径 例如:path: container fragment ->另一个片段
劣势:
导航图不再准确地代表用户流。
当我按了两次后退键时,我想到了这个问题。首先,我拦截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
}
}
当我第二次遇到这种情况时,它的状态与第一次相同,我发现我可能会使用add函数。让我们来分析一下这些情况。
首先,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
)
到目前为止,问题已经真正解决了。
在我的情况下,问题发生时,我重用了我的片段内的一个viewpager片段作为viewpager的子。 viewpager片段(父片段)被添加到Navigation xml中,但动作没有添加到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">
修正了添加动作到父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>
在我的情况下,我有多个导航图文件,我试图从一个导航图位置移动到另一个导航图的目的地。
为此,我们必须将第二个导航图像这样包含在第一个导航图中
<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">
在第二张图中。
更多信息请点击这里
似乎混合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导航。
更新的@Alex坚果解决方案
如果没有特定片段的操作,并希望导航到片段
fun NavController.navigateSafe(
@IdRes actionId: Int, @IdRes fragmentId: Int, args: Bundle? = null,
navOptions: NavOptions? = null, navExtras: Navigator.Extras? = null)
{
if (actionId != 0) {
val action = currentDestination?.getAction(actionId) ?: graph.getAction(actionId)
if (action != null && currentDestination?.id != action.destinationId) {
navigate(actionId, args, navOptions, navExtras)
}
} else if (fragmentId != 0 && fragmentId != currentDestination?.id)
navigate(fragmentId, args, navOptions, navExtras)
}
我得到了同样的错误,因为我使用了导航抽屉和getSupportFragmentManager(). begintransaction()。替换()在同一时间在我的代码某处。
我通过使用这个条件(测试是否目的地)摆脱了错误:
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()总是指向home片段。你必须只使用navigation . findnavcontroller (v).navigate(R.id.your_action)或nav图形控制器函数来处理你的所有导航操作。
一个荒谬但非常强大的方法是: 简单地称之为:
view?.findNavController()?.navigateSafe(action)
只需创建这个扩展:
fun NavController.navigateSafe(
navDirections: NavDirections? = null
) {
try {
navDirections?.let {
this.navigate(navDirections)
}
}
catch (e:Exception)
{
e.printStackTrace()
}
}
造成这个问题的原因可能有很多。 在我的情况下,我正在使用MVVM模型,我正在观察布尔导航 当布尔值为true时->导航 否则什么都不要做 这工作得很好,但这里有一个错误
当从目标片段按下返回按钮时,我遇到了同样的问题。问题是布尔对象,因为我忘记将布尔值更改为false,这造成了混乱。我刚刚在viewModel中创建了一个函数,将其值更改为false,并在findNavController()之后调用它
通常当这种情况发生在我身上时,我遇到了Charles Madere所描述的问题:在同一个ui上触发了两个导航事件,一个改变了currentDestination,另一个失败是因为currentDestination被改变了。 如果双击或单击两个视图,就会发生这种情况,其中有一个点击侦听器调用findNavController.navigate。
所以要解决这个问题,你可以使用if-checks, try-catch,或者如果你感兴趣,有一个findSafeNavController(),它在导航之前为你做这个检查。它还有一个检查,以确保您不会忘记这个问题。
GitHub
详细说明问题的文章
我写了这个扩展
fun Fragment.navigateAction(action: NavDirections) {
val navController = this.findNavController()
if (navController.currentDestination?.getAction(action.actionId) == null) {
return
} else {
navController.navigate(action)
}
}
在思考了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)
我为Fragment创建了这个扩展函数:
fun Fragment.safeNavigate(
@IdRes actionId: Int,
@Nullable args: Bundle? = null,
@Nullable navOptions: NavOptions? = null,
@Nullable navigatorExtras: Navigator.Extras? = null
) {
NavHostFragment.findNavController(this).apply {
if (currentDestination?.label == this@safeNavigate::class.java.simpleName) {
navigate(actionId, args, navOptions, navigatorExtras)
}
}
}
今天
def navigationVersion = "2.2.1"
这个问题仍然存在。我在Kotlin上的方法是:
// To avoid "java.lang.IllegalArgumentException: navigation destination is unknown to this NavController", se more https://stackoverflow.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)
}
}
如果你点击太快,它会导致null和崩溃。
我们可以使用RxBinding库来帮助这一点。你可以在点击发生之前添加节流和持续时间。
RxView.clicks(view).throttleFirst(duration, TimeUnit.MILLISECONDS)
.subscribe(__ -> {
});
这些关于在Android上节流的文章可能会有所帮助。干杯!
如果你使用的是recyclerview,只需在你的点击上添加一个点击监听器冷却时间,并在你的recyclerview xml文件中使用android:splitMotionEvents="false"
我解决了同样的问题,把检查之前导航,而不是样板代码点击即时控制
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://stackoverflow.com/a/56168225/7055259
您可以在导航之前检查请求导航的Fragment是否仍然是当前目的地,从这个要点出发。
它基本上是在片段上设置一个标记,以便稍后进行查找。
/**
* 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只是一个id,必须添加到ids.xml中,以确保它是唯一的。<item name="tag_navigation_destination_id" type="id" />
更多关于bug和解决方案的信息,以及navigateSafe(…)扩展方法在“修复可怕的”…是未知的NavController”
我通过检查当前目标中是否存在下一个操作来解决这个问题
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个不同的按钮
更新到@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"的动作,你不希望重复的目的地。
正如在其他回答中提到的,此异常通常发生在用户
同时单击处理导航的多个视图 在处理导航的视图上多次单击。
使用计时器来禁用单击并不是处理此问题的合适方法。如果用户在计时器过期后还没有导航到目的地,应用程序无论如何都会崩溃,在许多情况下,导航不是执行的动作,快速点击是必要的。
在情况1中,android:splitMotionEvents=“false”在xml或setMotionEventSplittingEnabled(false)在源文件应该有帮助。将此属性设置为false将只允许一个视图进行单击。你可以在这里阅读
在情况2中,会有一些东西延迟导航过程,允许用户多次单击视图(API调用,动画等)。如果可能的话,应该解决根本问题,以便即时进行导航,不允许用户两次单击视图。如果延迟是不可避免的,就像在API调用的情况下,禁用视图或使其不可点击将是适当的解决方案。
为了避免这种崩溃,我的一个同事写了一个小库,它公开了一个SafeNavController,一个围绕NavController的包装器,并处理由于多个导航命令同时发生崩溃的情况。
这里有一篇关于整个问题和解决方案的短文。
你可以在这里找到图书馆。
I am calling the 2.3.1 Navigation and the same error occurs when the application configuration changes. When the cause of the problem was found through Debug, the GaphId in NavHostFragment did not take effect as the ID currently set by calling navController.setGraph(). The GraphId of NavHostFragment can only be obtained from the <androidx.fragment.app.FragmentContainerView/> tag. At this time, this problem will occur if there are multiple GraphIds dynamically set in your code. When the interface is restored, the Destination cannot be found in the cached GraphId. You can solve this problem by manually specifying the value of mGraphId in NavHostFragment through reflection when switching Graph.
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();
}
还有另一种解决快速点击导航崩溃问题的方法:
fun NavController.doIfCurrentDestination(@IdRes destination: Int, action: NavController.()-> Unit){
if(this.currentDestination?.id == destination){action()}
}
然后像这样使用:
findNavController().doIfCurrentDestination(R.id.my_destination){ navigate(...) }
这种解决方案的好处是,您可以轻松地用您已经使用的任何签名包装任何现有的naagte()调用,而不需要进行一百万次重载
试试
创建以下扩展函数(或普通函数):
UPDATED(不带反射,可读性更强)
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)
}
}
}
}
老了(沉思)
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)
}
}
然后像这样使用你的片段:
val direction = FragmentOneDirections.actionFragmentOneToFragmentTwo()
// new usage
safeNavigateFromNavController(direction)
// old usage
// findNavController().safeNavigate<FragmentOne>(action)
我的问题是
我有一个片段(FragmentOne),它指向另外两个片段(FragmentTwo和FragmentThree)。在一些低级设备中,用户按下重定向到FragmentTwo的按钮,但在用户按下重定向到FragmentThree的按钮几毫秒后。结果是:
致命异常:java.lang.IllegalArgumentException导航 action/destination action_fragmentOne_to_fragmentTwo找不到 来自当前目的地 类= FragmentThree
我的答案是:
我检查当前目的地是否属于当前片段。如果为真,则执行导航操作。
就这些!
将我的答案优雅地扔到处理两种情况(双击,同时点击两个按钮)的环中,但尽量不掩盖真正的错误。
我们可以使用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)
}
我在我的项目中也有同样的问题,首先我试图在视图上触发导航动作的点击,但经过一些实验后,我发现在真正缓慢的设备上,debounce应该是一个非常高的值,导致应用程序对快速设备的用户感到缓慢。
所以我为NavController提出了以下扩展,我认为它符合原始的API,并且易于使用:
fun NavController.safeNavigate(directions: NavDirections) {
try {
currentDestination?.getAction(directions.actionId) ?: return
navigate(directions.actionId, directions.arguments, null)
} catch (e : Exception) {
logError("Navigation error", e)
}
}
fun NavController.safeNavigate(directions: NavDirections, navOptions: NavOptions?) {
try {
currentDestination?.getAction(directions.actionId) ?: return
navigate(directions.actionId, directions.arguments, navOptions)
} catch (e : Exception) {
logError("Navigation error", e)
}
}
fun NavController.safeNavigate(directions: NavDirections, navigatorExtras: Navigator.Extras) {
try {
currentDestination?.getAction(directions.actionId) ?: return
navigate(directions.actionId, directions.arguments, null, navigatorExtras)
} catch (e : Exception) {
logError("Navigation error", e)
}
}
请注意,我正在使用SafeArgs和NavDirections。这些函数检查操作从当前目的地是否有效,并且仅在操作不为空时进行导航。如果Navigation库每次都返回正确的操作,那么try catch部分就不需要了,但我希望消除所有可能的崩溃。
在我的情况下,我通过验证所有导航操作都正确地管理到各自的图形中,并更新了设备后退按钮的代码来解决这个问题,如下所示:
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()
}
}
})