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:
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.
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 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()
}
}
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.
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”
After thinking over Ian Lake's advice in this twitter thread I've came up with following approach. Having NavControllerWrapper
defined as such:
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()
}
Then in navigation code:
val navController = navControllerProvider.getNavController()
navController.navigate(from = R.id.main, to = R.id.action_to_detail)