Pressing on BottomNavigationBar item always creates a instance, and also keep the old one in backstack

28 Views Asked by At

I am using Android navigation component and BottomNavigationBar for my application.

The Implementation

I have four tabs and i have created a separated navigation graph for each of my tab and 1 navigation graph for authentication related fragments (login_navigation). and my navigation graph looks like this

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main_nav_graph"
app:startDestination="@+id/login_navigation">

<include app:graph="@navigation/login_navigation" />

<include app:graph="@navigation/dashboard_navigation" />
<include app:graph="@navigation/documents_navigation" />
<include app:graph="@navigation/templates_navigation" />
<include app:graph="@navigation/settings_navigation" />

</navigation>

My application flow is like this login_navigation -> (if user already authenticated) -> dashboard_navigation, and to open the dashboard i am using this code

findNavController().popBackStack()
findNavController().navigate(R.id.dashboard_navigation)

My Bottom Navigation Bar

Problem

If I go from Dashboard -> Documents -> Dashboard it creates a new instance of Dashboard fragment and its viewmodel, and also retain the old instance of Dashboard. If i press back it will go like this Clear the new Dashboard instance -> Documents -> Old Dashboard instance

Expectation Whenever i press Dashboard again, it should not create new instance but it should open the old instance.

How can i achieve this behaviour.

1

There are 1 best solutions below

0
Awais Abbas On

So after a lots of searching and reading documentation, I came up with a solution which is in my opinion pretty neat than my previous implementation, and it solves all of the following problems.

  1. Clicking on BottomNavigationView item, created new instance every time
  2. Circular navigation issue, where old instance stayed in memory and instead of using old instance, app created a new one
  3. Proper backstack handling and state restore, A -> B -> C if user presses A in bottom nav view it will not go back to A and was not pop C and B, which it should have
  4. Also it maintain separate backstack for each tab

Solution

  1. Instead of maintaining separate navigation graph files, i added all fragments in one file
  2. I created Global actions, for each of my tab
  3. Overridden setOntemSelectedListener and used these global action

nav_graph.xml

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_nav_graph"
app:startDestination="@+id/splashFragment">

    <!--Login Module Routes-->
    <fragment
     android:id="@+id/splashFragment"
     android:name="ui.fragments.splash.SplashFragment"
     tools:layout="@layout/fragment_splash"/>
     ....

    <!--Dashboard Routes-->
    <fragment
     android:id="@+id/dashboard_fragment"
     android:name="ui.fragments.dashboard.DashboardFragment"
     android:label="@string/MAIN_MENU_DASHBOARD"
     tools:layout="@layout/fragment_dashboard" />
     ....

    <!--Documents Routes-->
    <fragment
     android:id="@+id/documents_fragment"
     android:name="ui.fragments.documents.DocumentsFragment"
     android:label="@string/MAIN_MENU_DOCUMENTS"
     tools:layout="@layout/fragment_documents" />
    ...

    <!--Templates Routes-->
    <fragment
    android:id="@+id/templates_fragment"
    android:name="ui.fragments.templates.TemplatesFragment"
    android:label="@string/SERVICE_PLAN_PAGE_LABEL_TEMPLATES"
    tools:layout="@layout/fragment_templates" />
    ...

    <!--Settings Routes-->
    <fragment
    android:id="@+id/settings_fragment"
    android:name="ui.fragments.settings.SettingsFragment"
    android:label="@string/MENU_SETTINGS"
    tools:layout="@layout/fragment_settings">

    <action
        android:id="@+id/to_personal_info"
        app:destination="@id/personalInfoFragment" />
    </fragment>
    .....

    <!--Global Actions-->
    <action
     android:id="@+id/login_module_to_dashboard"
     app:destination="@id/dashboard_fragment"
     app:popUpTo="@id/splashFragment"
     app:popUpToInclusive="true" />

    <action
     android:id="@+id/action_global_to_documents_fragment"
     app:destination="@id/documents_fragment"
     app:restoreState="true" />

    <action
    android:id="@+id/global_dashboard"
    app:destination="@id/dashboard_fragment"
    app:popUpTo="@id/dashboard_fragment"
    app:popUpToSaveState="true"
    app:restoreState="true" />

    <action
    android:id="@+id/global_documents"
    app:destination="@id/documents_fragment"
    app:popUpTo="@id/dashboard_fragment"
    app:popUpToSaveState="true"
    app:restoreState="true" />

    <action
    android:id="@+id/global_templates"
    app:destination="@id/templates_fragment"
    app:popUpTo="@id/dashboard_fragment"
    app:popUpToSaveState="true"
    app:restoreState="true" />

    <action
    android:id="@+id/global_settings"
    app:destination="@id/settings_fragment"
    app:popUpTo="@id/dashboard_fragment"
    app:popUpToSaveState="true"
    app:restoreState="true" />

   </navigation>

BottomNavigationView Setup

private fun setupNavigationComponent() { val bottomNavigationView: BottomNavigationView = binding.bottomNavView

    val fragContainer =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    navController = fragContainer.navController

    bottomNavigationView.apply {
        setupWithNavController(navController)
        setOnItemSelectedListener { item ->
            when (item.itemId) {
                R.id.dashboard_fragment -> {
                    navController.navigate(R.id.global_dashboard)
                }

                R.id.documents_fragment -> {
                    navController.navigate(R.id.global_documents)
                }

                R.id.templates_fragment -> {
                    navController.navigate(R.id.global_templates)
                }

                R.id.settings_fragment -> {
                    navController.navigate(R.id.global_settings)
                }
            }
            true
        }
    }
}