Home > Blockchain >  BottomNavigationView with Jetpack Navigation not correctly showing the active menu indicator
BottomNavigationView with Jetpack Navigation not correctly showing the active menu indicator

Time:11-17

I'm trying to do solve some navigation problems of BottomNavigationView using Jetpack Navigation. I'm using Jetpack Navigation 2.4.0-beta02. The first problem is the back button always navigates me back to the start destination. This one is solved by adding menuCategory: secondary. However, there is another problem.

Let say I have 4 navigation menus of BottomNavigationView, fragment A, B, C, and D. And then, there is another fragment A1 which is not part of the BottomNavigationView. The nav graph looks like this:

<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/mobile_navigation"
    app:startDestination="@ id/navigation_a">

    <fragment
        android:id="@ id/navigation_a"
        android:name="com.example.FragmentA"
        android:label=""
        tools:layout="@layout/fragment_a">
        <action
            android:id="@ id/action_navigation_a_to_navigation_a1"
            app:destination="@id/navigation_a1"
            app:launchSingleTop="true" />
    </fragment>
    <fragment
        android:id="@ id/navigation_a1"
        android:name="com.example.FragmentA1"
        android:label=""
        tools:layout="@layout/fragment_a1" />
    <fragment
        android:id="@ id/navigation_b"
        android:name="com.example.FragmentB"
        android:label=""
        tools:layout="@layout/fragment_b" />
    <fragment
        android:id="@ id/navigation_c"
        android:name="com.example.FragmentC"
        android:label=""
        tools:layout="@layout/fragment_c" />
    <fragment
        android:id="@ id/navigation_d"
        android:name="com.example.FragmentD"
        android:label=""
        tools:layout="@layout/fragment_d" />
</navigation>

So, if I navigate from fragment A to A1, and then to fragment B (now the active menu indicator is at 2nd menu), then I press the hardware back button, it shows me fragment A1 which is correct. But, the active menu indicator is still at the 2nd menu instead of the 1st menu. I expected the active menu indicator at the 1st menu because I navigate to fragment A1 from fragment A.

This is my menu.xml file

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@ id/navigation_a"
        android:title="A"
        android:menuCategory="secondary" />

    <item
        android:title="B"
        android:id="@ id/navigation_b"
        android:menuCategory="secondary" />

    <item
        android:title="C"
        android:id="@ id/navigation_c"
        android:menuCategory="secondary" />

    <item
        android:title="D"
        android:id="@ id/navigation_d"
        android:menuCategory="secondary" />
</menu>

and MainActivity.kt

val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment_activity_main) as NavHostFragment
navController = navHostFragment.navController

binding.navView.setupWithNavController(navController)

Thanks for the help!

CodePudding user response:

Now the BottomNavigationView has 4 fragments: A, B, C, and D.

And you want to make the navigation like:

A (A highlighted) -> A1 -> B (A highlighted) -> Back pressed -> A1 (A highlighted)

But you got:

A (A highlighted) -> A1 -> B (A highlighted) -> Back pressed -> A1 (B highlighted)

So, you need to have A highlighted when you return back from B.

To do that you need to examine the previous fragment in the back stack when the back pressed at fragment B.

pseudo code:

// At fragment B
if (back_pressed) {

    if (previous_fragment is A1) {
        popup the back stack twice to return back to A
    } else {
        popup the back stack once (Normal behavior of the back stack)
    }

}

Code:

class FragmentB : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_b, container, false)

        requireActivity().onBackPressedDispatcher.addCallback(
            viewLifecycleOwner,
            object : OnBackPressedCallback(true) {
                override
                fun handleOnBackPressed() { // Back pressed
                    
                    val previousFragment = findNavController().previousBackStackEntry?.destination?.id
                    previousFragment?.let {
                        when (previousFragment) {
                            R.id.navigation_a1 ->
                                findNavController().navigateUp()
                            else ->
                                Log.d(TAG, "onCreateView: Not A1")
                        }
                        findNavController().navigateUp() // Normal behaviour when back is pressed
                    }
                                    
                }
            })

        return view

    }
}

Side Note:

This will work for the current navGraph setup; but I encourage you to have another graph, or even a nested navigation inside the current one; so that you keep the fragment A1 separated from the BottomNavView fragments.

UPDATE:

I want when pressing back, it still shows fragment A1 and the indicator is at the 1st menu. Your solution instead navigates me to fragment A.

To do so, you'd normally popup the back stack only once, and set the BottomNavView menu item to be checked:

Assuming the BottomNavView is hosted only by the activity, so you can access it with requireActivity() as MainActivity; cast that to the name of your activity.

Or if it's hosted by a fragment, then use parentFragment as MyParentFragment, similarly cast that to the name of the parent fragment.

At activity or parent fragment:

fun highlightAItem() {
    // Highlight A item from the bottomNavView
    val item = navView.menu.findItem(R.id.navigation_a)
    item.isChecked = true
}

At fragment B:

requireActivity().onBackPressedDispatcher.addCallback(
viewLifecycleOwner,
object : OnBackPressedCallback(true) {
    override
    fun handleOnBackPressed() {
        // Highlight A item from the BottomNavView
        (requireActivity() as MainActivity).highlightAItem() 
        // Pop backstack once to return to A1 fragment
        findNavController().navigateUp() 
    }
})
  • Related