Mulgard
Mulgard

Reputation: 10569

How to get android navigation host work together with BottomNavigationView?

I want to use the new navigation graph and the BottomNavigationView. I got the following dependencies:

// Navigation
implementation "androidx.navigation:navigation-fragment-ktx:$navVersion"
implementation "androidx.navigation:navigation-ui-ktx:$navVersion"
implementation "androidx.fragment:fragment:1.2.2"

// Material
implementation "com.google.android.material:material:$materialVersion"

notice the implementation "androidx.fragment:fragment:1.2.2" which should fix the error occuring with FragmentContainerView not appearing as host in the navigation graph.

Here is my simple navigation graph:

enter image description here

The first strange thing you can see here is that nothing is appearing as host, even though I added the dependency which should fix it.

My main activity layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity">

    <androidx.fragment.app.FragmentContainerView
            android:id="@+id/nav_host_fragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            app:defaultNavHost="true"
            app:navGraph="@navigation/navigation_graph" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/bottom_navigation"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="?colorPrimary"
            app:itemIconTint="@drawable/menu_navigation"
            app:itemTextColor="@drawable/menu_navigation"
            app:menu="@menu/menu_navigation" />

</LinearLayout>

and my main activity kotlin file:

class MainActivity : AppCompatActivity() {

    private var navController: NavController? = null
    private var appBarConfiguration: AppBarConfiguration? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main)

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

        appBarConfiguration = AppBarConfiguration.Builder(
            setOf(
                R.id.main_fragment,
                R.id.recycler_fragment,
                R.id.map_fragment
            )
        ).build()

        setupActionBar()
        setupBottomNavigationMenu()
    }

    private fun setupActionBar() {
        NavigationUI.setupActionBarWithNavController(
            this,
            navController!!,
            appBarConfiguration!!
        )
    }

    private fun setupBottomNavigationMenu() {
        bottom_navigation?.let {
            NavigationUI.setupWithNavController(it, navController!!)
        }
    }

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.menu_toolbar, menu)

        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        val navigated = NavigationUI.onNavDestinationSelected(item, navController!!)

        return navigated || super.onOptionsItemSelected(item)
    }
}

Here we also have my menu and my toolbar:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
    <item
            android:id="@+id/destination_home"
            android:icon="@drawable/ic_home"
            android:title="@string/home"
            tools:layout="@layout/fragment_main" />
    <item
            android:id="@+id/destination_recycler"
            android:icon="@drawable/ic_list"
            android:title="@string/recycler"
            tools:layout="@layout/fragment_recycler" />
    <item
            android:id="@+id/destination_map"
            android:icon="@drawable/ic_map"
            android:title="@string/map"
            tools:layout="@layout/fragment_map" />
</menu>
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
            android:id="@+id/destination_settings"
            android:icon="@drawable/ic_settings"
            android:title="@string/settings"
            app:showAsAction="ifRoom" />
</menu>

When I now start the app I would expect the following behavior:

  1. The app starts and shows the fragment with text "Home"
  2. The app would not show a back button in the toolbar
  3. When I navigate from "Home" to "Map" the toolbar would not show a back button in the toolbar
  4. When I navigate from "Home" to "Settings" the toolbar would show a back button in the toolbar
  5. When I navigate back from "Settings" the "Home" screen should appear
  6. When I navigate from "Map" to "Settings" the toolbar would show a back button in the toolbar
  7. When I navigate back from "Settings" the "Map" screen should now appear

What actually happens is:

  1. The app starts and shows the fragment with text "Home"
  2. The app shows a back button in the toolbar
  3. When I navigate from "Home" to "Map" the toolbar does show a back button in the toolbar
  4. When I navigate from "Home" to "Settings" the toolbar shows a back button in the toolbar
  5. When I navigate back from "Settings" the "Home" screen appears
  6. When I navigate from "Map" to "Settings" the toolbar shows a back button in the toolbar
  7. When I navigate back from "Settings" the instead "Map" the "Home" screen appears

So something is really messed up here with the navigation and I have no idea how to fix it.

Upvotes: 1

Views: 629

Answers (1)

ianhanniballake
ianhanniballake

Reputation: 199805

Firstly, you are passing R.id.main_fragment, R.id.recycler_fragment, R.id.main_fragment to your AppBarConfiguration - besides including main_fragment twice, those names don't match up with the destination_home, destination_recycler, and destination_map in your menu XML. Since your clicking in your bottom nav bar works, those appear to be the correct destination IDs and you should change your AppBarConfiguration to use the same IDs:

appBarConfiguration = AppBarConfiguration.Builder(
        setOf(
            R.id.destination_home,
            R.id.destination_recycler,
            R.id.destination_map
        )
    ).build()

Or, if you're using the navigation-ui-ktx artifact, you'd use the top level AppBarConfiguration creator:

appBarConfiguration = AppBarConfiguration(setOf(
    R.id.destination_home,
    R.id.destination_recycler,
    R.id.destination_map))

As per the onNavDestinationSelected() Javadoc:

By default, the back stack will be popped back to the navigation graph's start destination. Menu items that have android:menuCategory="secondary" will not pop the back stack.

Therefore it is expected that leaving out that in your menu XML for the Settings screen would cause you to go back to the start destination of your graph when you hit the back button. You'd want to change your menu XML to include that flag:

    <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
            android:id="@+id/destination_settings"
            android:icon="@drawable/ic_settings"
            android:title="@string/settings"
            app:showAsAction="ifRoom"
            android:menuCategory="secondary" />
</menu>

Upvotes: 1

Related Questions