'Is there a way to keep fragment alive when using BottomNavigationView with new NavController?

I'm trying to use the new navigation component. I use a BottomNavigationView with the navController : NavigationUI.setupWithNavController(bottomNavigation, navController)

But when I'm switching fragments, they are each time destroy/create even if they were previously used.

Is there a way to keep alive our main fragments link to our BottomNavigationView?



Solution 1:[1]

Update 19.05.2021 Multiple backstack
Since Jetpack Navigation 2.4.0-alpha01 we have it out of the box. Check Google Navigation Adavanced Sample

Old answer:
Google samples link Just copy NavigationExtensions to your application and configure by example. Works great.

Solution 2:[2]

After many hours of research I found solution. It was all the time right in front of us :) There is a function: popBackStack(destination, inclusive) which navigate to given destination if found in backStack. It returns Boolean, so we can navigate there manually if the controller won't find the fragment.

if(findNavController().popBackStack(R.id.settingsFragment, false)) {
        Log.d(TAG, "SettingsFragment found in backStack")
    } else {
        Log.d(TAG, "SettingsFragment not found in backStack, navigate manually")
        findNavController().navigate(R.id.settingsFragment)
    }

Solution 3:[3]

If you have trouble passing arguments add:

fragment.arguments = args

in class KeepStateNavigator

Solution 4:[4]

Not available as of now.

As a workaround you could store all your fetched data into view model and have that data readly available when you recreate the fragment. Make sure you get the view using activities context.

You can use LiveData to make your data lifecycle-aware observable

Solution 5:[5]

If you are here just to maintain the exact RecyclerView scroll state while navigating between fragments using BottomNavigationView and NavController, then there is a simple approach that is to store the layoutManager state in onDestroyView and restore it on onCreateView

I used ActivityViewModel to store the state. If you are using a different approach make sure you store the state in the parent activity or anything which survives longer than the fragment itself.

Fragment

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    recyclerview.adapter = MyAdapter()
    activityViewModel.listStateParcel?.let { parcelable ->
        recyclerview.layoutManager?.onRestoreInstanceState(parcelable)
        activityViewModel.listStateParcel = null
    }
}

override fun onDestroyView() {
    val listState = planet_list?.layoutManager?.onSaveInstanceState()
    listState?.let { activityViewModel.saveListState(it) }
    super.onDestroyView()
}

ViewModel

var plantListStateParcel: Parcelable? = null

fun savePlanetListState(parcel: Parcelable) {
    plantListStateParcel = parcel
}

Solution 6:[6]

I've used the link provided by @STAR_ZERO and it works fine. For those who having problem with the back button, you can handle it in the activity / nav host like this.

override fun onBackPressed() {
        if(navController.currentDestination!!.id!=R.id.homeFragment){
            navController.navigate(R.id.homeFragment)
        }else{
            super.onBackPressed()
        }
    }

Just check whether current destination is your root / home fragment (normally the first one in bottom navigation view), if not, just navigate back to the fragment, if yes, only exit the app or do whatever you want.

Btw, this solution need to work together with the solution link above provided by STAR_ZERO, using keep_state_fragment.

Solution 7:[7]

The solution provided by @piotr-prus helped me, but I had to add some current destination check:

if (navController.currentDestination?.id == resId) {
    return       //do not navigate
}

without this check current destination is going to recreate if you mistakenly navigate to it, because it wouldn't be found in back stack.

Solution 8:[8]

Super easy solution for custom general fragment navigation:

Step 1

Create a subclass of FragmentNavigator, overwrite instantiateFragment or navigate as you need. If we want fragment only create once, we can cache it here and return cached one at instantiateFragment method.

Step 2

Create a subclass of NavHostFragment, overwrite createFragmentNavigator or onCreateNavController, so that can inject our customed navigator(in step1).

Step 3

Replace layout xml FragmentContainerView tag attribute from android:name="com.example.learn1.navigation.TabNavHostFragment" to your customed navHostFragment(in step2).

Solution 9:[9]

In the latest Navigation component release - bottom navigation view will keep track of the latest fragment in stack.

Here is a sample:

https://github.com/android/architecture-components-samples/tree/main/NavigationAdvancedSample

Example code
In project build.gradle

dependencies {  
      classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.4.0-alpha01"
}

In app build.gradle

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'androidx.navigation.safeargs'
}

dependencies {
implementation "androidx.navigation:navigation-fragment-ktx:2.4.0-alpha01"
implementation "androidx.navigation:navigation-ui-ktx:2.4.0-alpha01"

}

Inside your activity - you can setup navigation with toolbar & bottom navigation view

val navHostFragment = supportFragmentManager.findFragmentById(R.id.newsNavHostFragment) as NavHostFragment
val navController = navHostFragment.navController
 //setup with bottom navigation view
binding.bottomNavigationView.setupWithNavController(navController)
//if you want to disable back icon in first page of the bottom navigation view
val appBarConfiguration = AppBarConfiguration(
    setOf(
                R.id.feedFragment,
                R.id.favoriteFragment
            )
        ).
//setup with toolbar back navigation
binding.toolbar.setupWithNavController(navController, appBarConfiguration)

Now in your fragment, you can navigate to your second frgment & when you deselect/select the bottom navigation item - NavController will remember your last fragment from the stack.

Example: In your Custom adapter

adapter.setOnItemClickListener { item ->
            findNavController().navigate(
                R.id.action_Fragment1_to_Fragment2
       )
}

Now, when you press back inside fragment 2, NavController will pop fragment 1 automatically.

https://developer.android.com/guide/navigation/navigation-navigate

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Piotr Prus
Solution 3 Victorlopesjg
Solution 4 Samuel Robert
Solution 5 Sai
Solution 6 veeyikpong
Solution 7 akhris
Solution 8
Solution 9