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()
}
})