可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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:
java.lang.IllegalArgumentException: navigation destination XXX
is unknown to this NavController
Every other navigation works fine except this particular one.
I use:
findNavContoller()
extension function of Fragment to get access to the navControler.
Any help will be appreciated.
回答1:
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
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 especially easy to do when you have a list of items. The above info on multiple click prevention will handle this case.
回答2:
Check currentDestination
before calling navigate might be helpful.
For example, if you have two fragment destinations on the navigation graph fragmentA
and fragmentB
, and there is only one action from fragmentA
to fragmentB
. calling navigate(R.id.action_fragmentA_to_fragmentB)
will result in IllegalArgumentException
when you were already on fragmentB
. Therefor you should always check the currentDestination
before navigating.
if (navController.currentDestination?.id == R.id.fragmentA) {
navController.navigate(R.id.action_fragmentA_to_fragmentB)
}
回答3:
You can check requested action in current destination of navigation controller.
UPDATE
added usage of global actions for safe navigation.
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)
}
}
回答4:
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
Since in the ViewPager the fragments are not a destination of A, your graph wouldn't know you are on B.
A solution can be to use ADirections in B to navigate to C
回答5:
In my case I was using a custom back button for navigating up. I called onBackPressed()
in stead of the following code
findNavController(R.id.navigation_host_fragment).navigateUp()
This caused the IllegalArgumentException
to occur. After I changed it to use the navigateUp()
method in stead, I didn't have a crash again.
回答6:
What I did to prevent the crash is the following:
I have a BaseFragment, in there I've added this fun
to ensure that the destination
is known by the currentDestination
:
fun navigate(destination: NavDirections) = with(findNavController()) {
currentDestination?.getAction(destination.actionId)
?.let { navigate(destination) }
}
Worth noting that I'm using the SafeArgs plugin.
回答7:
In my case, the issue occurred when I had re-used one of my Fragments inside a viewpager
fragment as a child of the viewpager
.
The viewpager
Fragment(which was the parent fragment) was added in the Navigation xml, but the action was not added in the viewpager
parent 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">
Fixed the issue by adding the action to the parent viewpager fragment also as shown below:
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>
回答8:
In my case the bug ocurred because I had a navigation action with the Single Top
and the Clear Task
options enabled after a splash screen.
回答9:
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
回答10:
TL;DR Wrap your navigate
calls with try-catch
(simple way), or make sure there will be only one call of navigate
in short period of time. This issue likely won't go away. Copy bigger code snippet in your app and try out.
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:
- First
navigate
invocation always works fine;
- Second and all other invocations of the
navigate
method resolve in 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
.
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.
This exact error can be found in NavController.java
method findDestination
.
回答11:
It occurs to me when I press the back button two times. At first, I intercept KeyListener
and override KeyEvent.KEYCODE_BACK
. I added the code below in the function named OnResume
for the Fragment, and then this question/issue is solved.
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
}
}
When it happens to me for a second time, and it's status is the same as the first one, I find that I maybe use the adsurd
function. Let’s analyze these situations.
Firstly, FragmentA navigates to FragmentB ,then FragmentB navigates to FragmentA, then press back button... the crash appears.
Secondly, FragmentA navigates to FragmentB, then FragmentB navigates to FragmentC, FragmentC navigates to FragmentA, then press back button... the crash appears.
So I think when pressing back button, FragmentA will return to FragmentB or FragmentC, then it causes the login mess. Finally I find that the function named popBackStack
can be used for back rather than navigate.
NavHostFragment.findNavController(this@TeacherCloudResourcesFragment).
.popBackStack(
R.id.teacher_prepare_lesson_main_fragment,false
)
So far, the problem is really solved.
回答12:
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.
回答13:
This happened to me, my issue was I was clicking a FAB on tab item fragment
. I was trying to navigate from one of tab item fragment to another fragment
.
But according to Ian Lake in this answer we have to use tablayout
and viewpager
, no navigation component support. Because of this, there is no navigation path from tablayout containing fragment to tab item fragment.
ex:
containing fragment -> tab layout fragment -> tab item fragment -> another fragment
Solution was to create a path from tab layout containing fragment to intended fragment
ex: path: container fragment -> another fragment
Disadvantage:
- Nav graph no longer represent user flow accurately.
回答14:
In my case I got that error when tried to navigate from another thread, in 50% of cases. Run the code on main thread helps
requireActivity().runOnUiThread {
findNavController().navigate(...)
}
回答15:
In my case this occured when I accidentally added a +
in action destination , and the crash only occured when I went to same fragment multiple times .
<action
android:id="@+id/action_to_profileFragment"
app:destination="@+id/profileFragment" />
Solution is to remove +
from action destination, use only @id/profileFragment
instead of @+id/profileFragment
<action
android:id="@+id/action_to_profileFragment"
app:destination="@id/profileFragment" />
回答16:
In my case, I had multiple nav graph files and I was trying to move from 1 nav graph location to a destination in another nav graph.
For this we have to include the 2nd nav graph in the 1st one like this
<include app:graph="@navigation/included_graph" />
and add this to your action:
<action
android:id="@+id/action_fragment_to_second_graph"
app:destination="@id/second_graph" />
where second_graph
is :
<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">
in the second graph.
More info here
回答17:
It seems that mixing fragmentManager control of the backstack and Navigation Architecture control of the backstack can cause this issue also.
For example the original CameraX basic sample used fragmentManager backstack navigation as below and it appears as if it did not correctly interact with Navigation:
// Handle back button press
view.findViewById<ImageButton>(R.id.back_button).setOnClickListener {
fragmentManager?.popBackStack()
}
If you log the 'current destination' with this version before moving from the main fragment (the camera fragment in this case) and then log it again when you return to the main fragment, you can see from the id in the logs that the id is not the same. At a guess, the Navigation updated it when moving to the fragment and the fragmntManager did not then update it again when moving back. From the logs:
Before: D/CameraXBasic: currentDest?: androidx.navigation.fragment.FragmentNavigator$Destination@b713195
After: D/CameraXBasic: currentDest?: androidx.navigation.fragment.FragmentNavigator$Destination@9807d8f
The updated version of CameraX basic sample uses Navigation to return like this:
// Handle back button press
view.findViewById<ImageButton>(R.id.back_button).setOnClickListener {
Navigation.findNavController(requireActivity(), R.id.fragment_container).navigateUp()
}
This works correctly and the logs show the same id when back at the main fragment.
Before: D/CameraXBasic: currentDest?: androidx.navigation.fragment.FragmentNavigator$Destination@b713195
After: D/CameraXBasic: currentDest?: androidx.navigation.fragment.FragmentNavigator$Destination@b713195
I suspect the moral of the story, at least at this time, is to be very careful mixing Navigation with fragmentManager navigation.
回答18:
Updated @Alex Nuts solution
If there is no action for particular fragment and want to navigate to fragment
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)
}