IllegalArgumentException: navigation destination xxx is unknown to this NavController

Kotlin Programming

Question or issue of Kotlin Programming:

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 findNavController() function of Fragment to get access to the NavController.

Any help will be appreciated.

How to solve this issue?

Solution no. 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.

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.

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

Solution no. 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)
}

Solution no. 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)
    }
}

Solution no. 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

Solution no. 5:

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.

Solution no. 6:

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.

Solution no. 7:

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:

  1. First navigate invocation always works fine;
  2. 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.

Solution no. 8:

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

    //issue got fixed when i added this action to the viewpager parent also
    

....
// viewpager parent fragment

Fixed the issue by adding the action to the parent viewpager fragment also as shown below:

nav.xml
//reused fragment

    //issue got fixed when i added this action to the viewpager parent also
    

....
// viewpager parent fragment

    

Solution no. 9:

Today


def navigationVersion = “2.2.1”

The issue still exists. My approach on Kotlin is:

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

Solution no. 10:

You can check before navigating if the Fragment requesting the navigation is still the current destination, taken from this gist.

It basically sets a tag on the fragment for later lookup.

/**
 * 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 is just an id you’ll have to add to your ids.xml, to make sure it’s unique. <item name="tag_navigation_destination_id" type="id" />

More info on the bug and the solution, and navigateSafe(...) extention methods in “Fixing the dreaded “… is unknown to this NavController”

Solution no. 11:

I have resolved the same problem by putting check before navigate instead of boilerplate code for clicking instantly control

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

according to this answer

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

Solution no. 12:

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.

Solution no. 13:

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


and add this to your action:


where second_graph is :


in the second graph.

More info here

Solution no. 14:

In order to avoid this crash one of my colleagues wrote a small library which exposes a SafeNavController, a wrapper around the NavController and handles the cases when this crash occurs because of multiple navigate commands at the same time.

Here is a short article about the whole issue and the solution.

You can find the library here.

Solution no. 15:

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(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?: [email protected]95
After: D/CameraXBasic: currentDest?: [email protected]8f

The updated version of CameraX basic sample uses Navigation to return like this:

 // Handle back button press
        view.findViewById(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?: [email protected]95
After: D/CameraXBasic: currentDest?: [email protected]95

I suspect the moral of the story, at least at this time, is to be very careful mixing Navigation with fragmentManager navigation.

Solution no. 16:

A Ridiculous way but very powerful is:
Simply call this:

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

Just Create this Extention:

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

Solution no. 17:

I got this same error because I used a Navigation Drawer and getSupportFragmentManager().beginTransaction().replace( ) at the same time somewhere in my code .

I got rid of the error by using this condition(testing if the destination) :

if (Navigation.findNavController(v).getCurrentDestination().getId() == R.id.your_destination_fragment_id)
Navigation.findNavController(v).navigate(R.id.your_action);

In my case the previous error was triggered when I was clicking on the navigation drawer options. Basically the code above did hide the error , because in my code somewhere I used navigation using getSupportFragmentManager().beginTransaction().replace( ) The condition –

 if (Navigation.findNavController(v).getCurrentDestination().getId() ==
  R.id.your_destination_fragment_id) 

was never reached because (Navigation.findNavController(v).getCurrentDestination().getId() was always poiting to home fragment. You must only use Navigation.findNavController(v).navigate(R.id.your_action) or nav graph controller functions for all your navigation actions.

Solution no. 18:

I resolve this issue by checking if the next action exist in the current destination

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

This resolve a problem if the user click fast on 2 differents button

Solution no. 19:

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

Solution no. 20:

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.

Hope this helps!