androidbottomnavigationviewandroid-architecture-navigation

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


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.


Solution

  • 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
            }
        }
    }