Navigate Back with Navigation Component

Or how to do that without creating a new instance of Fragment

Michael Spitsin
4 min readSep 27, 2019

As we all know, Google introduced Navigation Components some time ago. And moreover there is already 2.1.0 version at the moment. But there is small problem: we can not go back to specific fragment without its recreation. So this small article will be about workaround, you can use to eliminate such flaw.

The problem

Suppose we have One Activity Several fragments navigation (by the way I wrote about such pattern previously, you can check that here). And suppose we have 3 fragments. Fragment one has its own state, which can be saved and restore, and fragment three contain a button to go back to the first one. You can check base project here(code) and here(xml).

To navigate from third fragment to first one we use navigation action with attribute popUpTo:

<action
android:id="@+id/action_fragment_3_to_fragment_1"
app:destination="@id/fragment_1"
app:enterAnim="@anim/anim_slide_pop_exit_fragment"
app:exitAnim="@anim/anim_slide_exit_fragment"
app:popEnterAnim="@anim/anim_slide_pop_enter_fragment"
app:popExitAnim="@anim/anim_slide_pop_exit_fragment"
app:popUpTo="@+id/fragment_1"
/>

To understand the current problem we need to change state of first fragment to be different from default one, then go to the third fragment, and then press a button to navigate back to the first one and BAM, you loose you saved state and have default one again:

Magic disappear of 5

Moreover if we slow our open/close fragment animations you will see, that the animation of going back to first fragment is not really smooth:

Sliding not to first page, but with first page

Understanding

That happens because first fragment is getting deleted and recreate from scratch. Let’s go to source code of Navigation library to check that.

Actually all basic navigation work starts, when you call NavController.navigate. At this point 2 main steps happens (at least two main steps, that are important for us):

  • Popup back stack until we reach fragment specified by popUpTo attribute
  • Create new fragment, specified by destinationId attribute

Eventually we will come to FragmentNavigator.navigate method, which creates needed fragment inside. So actually without specifying popUpInclusive attribute we will have new instance of Fragment1 with old instance, lying in the back stack. With using this attribute, old fragment will be permanently deleted and replaced by new one.

Solution

Unfortunately I didn’t found any elegant solution:

  • Y̶o̶u̶ ̶c̶a̶n̶ ̶n̶o̶t̶ ̶a̶v̶o̶i̶d̶ ̶w̶r̶i̶t̶i̶n̶g̶ ̶d̶e̶s̶t̶i̶n̶a̶t̶i̶o̶n̶I̶d̶,̶ ̶s̶i̶n̶c̶e̶ ̶t̶h̶i̶s̶ ̶a̶t̶t̶r̶i̶b̶u̶t̶e̶ ̶i̶s̶ ̶r̶e̶q̶u̶i̶r̶e̶d̶ . Actually this is not true. You can!! And this is actual right solution. Check the UPD in the end of article
  • You can not use any other attributes
  • You can not adjust FragmentNavigator for your needs

One thing you can do, that will help us, is to specify your own Navigator. So let’s do that.

First, describe you NavHostFragment:

class MyNavHostFragment : NavHostFragment() {
override fun createFragmentNavigator() =
MyFragmentNavigator(requireContext(), childFragmentManager, id)
}

Next, create your own Navigator, that will inherit fragment navigator. The main change here will be checking wether popUpTo is equal to destination. And if yes, that probably means (at least in our project) that you just want to go back to that fragment without recreation, so you can just return navDest without changing:

@Navigator.Name("fragment")
class MyFragmentNavigator(
context: Context,
fm: FragmentManager,
containerId: Int
) : FragmentNavigator(context, fm, containerId) {

override fun navigate(...): NavDestination? {
val shouldSkip = navOptions?.run {
popUpTo == destination.id && !isPopUpToInclusive
} ?: false

return if
(shouldSkip) null
else super
.navigate(destination, args, navOptions, navigatorExtras)
}
}

Don’t forget to place @Navigator.Name annotation on class with name fragment, so that your navigator will be used as FragmentNavigator instead of default one.

And the last thing override method of creating fragment navigator in NavHostFragment. And use that fragment instead of default one in your xml our whatever place.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
>

<fragment
android:id="@+id/navFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.github.backnavigation.MyNavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/navigation"
/>

</FrameLayout>

Now lets check our flow with new awesome approach ;)

Update at the end

While I wrote that article, I’ve checked the source code of Navigation component one more time and realized, that I was completely blind :(

As appeared, you may not specify destination attribute, but specify popUpTo attribute. In that case NavController will know, that you want just to go back till fragment, specified in popUpTo attribute without creating anything else.

So the only one right solution in this problem, which I described at the beginning of the article, is:

<action 
android:id="@+id/action_fragment_3_to_fragment_1"
app:enterAnim="@anim/anim_slide_pop_exit_fragment"
app:exitAnim="@anim/anim_slide_exit_fragment"
app:popEnterAnim="@anim/anim_slide_pop_enter_fragment"
app:popExitAnim="@anim/anim_slide_pop_exit_fragment"
app:popUpTo="@+id/fragment_1"
/>

Instead of

<action
android:id="@+id/action_fragment_3_to_fragment_1"
app:destination="@id/fragment_1"
app:enterAnim="@anim/anim_slide_pop_exit_fragment"
app:exitAnim="@anim/anim_slide_exit_fragment"
app:popEnterAnim="@anim/anim_slide_pop_enter_fragment"
app:popExitAnim="@anim/anim_slide_pop_exit_fragment"
app:popUpTo="@+id/fragment_1"
/>

So this whole article is becoming useless, but I just didn’t want to throw my little research away, considering the fact that I already wrote almost everything and I had no other material on that month. :(

--

--

Michael Spitsin

Love being creative to solve some problems with an simple and elegant ways