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.
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
}
}
fun View.setOnSingleClickListener(l: View.OnClickListener) {
setOnClickListener(OnSingleClickListener(l))
}
fun View.setOnSingleClickListener(l: (View) -> Unit) {
setOnClickListener(OnSingleClickListener(l))
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
settingsButton.setOnSingleClickListener {
// navigation call here
}
}
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)
}
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)
}
}
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.
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
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.
Try that
UPDATED (without reflection and more readable)
import androidx.fragment.app.Fragment
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import androidx.navigation.fragment.FragmentNavigator
fun Fragment.safeNavigateFromNavController(directions: NavDirections) {
val navController = findNavController()
val destination = navController.currentDestination as FragmentNavigator.Destination
if (javaClass.name == destination.className) {
navController.navigate(directions)
}
}
OLD (with reflection)
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)
My problem was
I have a fragment (FragmentOne) that goes to two others fragments (FragmentTwo and FragmentThree). In some low devices, the user press button that redirects to FragmentTwo but in few milliseconds after the user press button that redirects to FragmentThree. The results is:
Fatal Exception: java.lang.IllegalArgumentException Navigation action/destination action_fragmentOne_to_fragmentTwo cannot be found from the current destination Destination(fragmentThree) class=FragmentThree
My solve was:
I check if the current destination belongs to the current fragment. If true, I execute the navigation acion.
That is all!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With