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:
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
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.
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>
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.
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)
}
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)
}