Android Navigation - Safely navigate screens on button clicks
All of us use the android navigation component in our project to navigate from one screen to another easily.
In some cases, you may need to navigate to another screen on a button-click event. To do that, you may do like the below code.
binding.navigateButton.setOnClickListener {
findNavController()
.navigate(R.id.action_firstFragment_to_secondFragment)
}
The above code won't cause any issues in simple use cases.
But if you click the button repetitively and your destination screen takes some time to load, you will end up with IllegalArgumentException
.
Fatal Exception: java.lang.IllegalArgumentException: Navigation action/destination io.github.dhina17.template:id/action_firstFragment_to_secondFragment cannot be found from the current destination Destination(io.github.dhina17.template:id/secondFragment) label=SecondFragment
Because, on your first click, the current destination is changed to the target destination (Second Fragment) in the navigation controller but the source screen (First Fragment) is still alive due to the load time of the target destination screen.
So your further clicks on the button lead to telling the navigation controller to do the navigation action i.e action_firstFragment_to_secondFragment
from the Second Fragment. But that action action_firstFragment_to_secondFragment
is not present in the Second Fragment so using that action causes IllegalArgumentException
.
So your app will be crashed.
Let's see how to prevent this issue.
Solution 1 - Use try-catch
As usual, catching the exception will prevent your app from crashing.
binding.navigateButton.setOnClickListener {
try {
findNavController()
.navigate(R.id.action_firstFragment_to_secondFragment)
} catch (e: Exception) {
// No need to do anything.
}
}
Looks ugly yes? Create a kotlin extension function to the NavController
to avoid repeating code.
fun NavController.safeNavigate(@IdRes actionId: Int) {
try {
navigate(actionId)
} catch (e: Exception) {
// No need to do anything.
}
}
// Then use the extension function on the button click
binding.navigateButton.setOnClickListener {
findNavController()
.safeNavigate(R.id.action_firstFragment_to_secondFragment)
}
Solution 2 - Check the current destination before navigating
Check if the action is present in the current destination or not before navigating by writing an extension function.
fun NavController.safeNavigate(@IdRes actionId: Int) {
currentDestination?.getAction(actionId)?.run { navigate(direction) }
}
// Then use the extension function on the button click
binding.navigateButton.setOnClickListener {
findNavController()
.safeNavigate(R.id.action_firstFragment_to_secondFragment)
}
Note: I ain't the author of this code. I found it somewhere on the internet.
Thanks for reading. <3
Dhina17