0

Facing an issue where using navController.navigate(R.id.tab_dashboard) to dynamically switch to a bottom navigation tab messes up with bottom navigation.

Attached a video to explain the issue. Video link: https://github.com/hishamMuneer/BottomNavSample/blob/master/video/bottomnav_issue.mp4 Sample code here: https://github.com/hishamMuneer/BottomNavSample.git


class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

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

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val navView: BottomNavigationView = binding.navView

        val navController = findNavController(R.id.nav_host_fragment_activity_main)
        // Passing each menu ID as a set of Ids because each
        // menu should be considered as top level destinations.
        val appBarConfiguration = AppBarConfiguration(
            setOf(
                R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications
            )
        )
        setupActionBarWithNavController(navController, appBarConfiguration)
        navView.setupWithNavController(navController)

        val fab = binding.fab
        fab.setOnClickListener {
            navController.navigate(R.id.navigation_dashboard)
        }
    }
}
Hisham Muneer
  • 8,558
  • 10
  • 54
  • 79

3 Answers3

2

You can use the code below to enable dynamic transition when Fab is clicked.

binding.navView.selectedItemId = R.id.navigation_dashboard

your current code causes destination to break.

Arda Kazancı
  • 8,341
  • 4
  • 28
  • 50
1

I want to understand why calling navController.navigate(R.id.tab_dashboard) is messing up with the normal tap on the on tabs.

Disclaimer: This answer targets the demonstrations why this behavior happens per OP request. @Arda Kazancı thankfully already provides the answer.


Navigating to some BottonNavigationView fragment using a NavController action doesn't actually achieve a real BottonNavigationView menu item selection; instead it does a fragment transaction on the fragment placeholder, i.e., the NavHostFragment.

That is because the action has just abstract information to the destination fragment whether the destination fragment is a part of BottomNavigationView, or just a normal fragment.

So, when using the action navController.navigate(R.id.action_global_dashBoardFragment) will not make the navView.setupWithNavController(navController) do its job, or at least will distub it.

In your shared video, after hitting the fab, and then trying to hit the unsuccessful selection Home tab, that doesn't do a true selection because the NavigationBarView.OnItemSelectedListener callback returns false (keeping the current Dashboard tab selected instead of the Home fragment).

This can be demonstrated by using a wrapper around the NavigationBarView.OnItemSelectedListener interface to log the returned value of the onNavigationItemSelected() callback:

Use the below custom MyBottomNavigationView in the activity_main.xml layout instead of the <com.google.android.material.bottomnavigation.BottomNavigationView:

class MyBottomNavigationView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null
) : BottomNavigationView(context, attrs) {

    override fun setOnItemSelectedListener(listener: OnItemSelectedListener?) {
        // wrapper to log the returned value of the default listener 
        super.setOnItemSelectedListener(OnItemSelectedListenerWrapper(listener))
    }

}

The wrapper interface:

import com.google.android.material.navigation.NavigationBarView

class OnItemSelectedListenerWrapper(private val listener: NavigationBarView.OnItemSelectedListener?) :
    NavigationBarView.OnItemSelectedListener {
    override fun onNavigationItemSelected(item: MenuItem): Boolean {
        val returnedValue = listener!!.onNavigationItemSelected(item)
        Log.d("LOG_TAG", "onNavigationItemSelected: $returnedValue")
        return returnedValue
    }
}

Now catching that log by doing the exact scenario shared in your video:

 D  onNavigationItemSelected: true
 D  onNavigationItemSelected: true
 D  onNavigationItemSelected: true
 D  onNavigationItemSelected: true
 D  onNavigationItemSelected: true
 D  onNavigationItemSelected: true
 D  onNavigationItemSelected: true
 D  onNavigationItemSelected: true
 D  onNavigationItemSelected: true
 D  onNavigationItemSelected: true
 D  onNavigationItemSelected: false  // <<<<< Here the Home tab is hit, but it keeps on the Dashboard

The callback returns true (i.e., it's a successful tab selection) until hitting the fab just before the last log which returns false (which is not considered a tab selection, so keep the current tab selected) which occurs when the Home tab is hit, but the Dashboard keeps on because the fab action (R.id.action_global_dashBoardFragment) already disturbed the functionality of the navView.setupWithNavController(navController).

The answer is to never use actions with BottomNavigationView fragments which are automatically managed by the NavController with setupWithNavController(), but to imitate using the tab pragmatically as demonstrated by @Arda Kazancı in order to make the automation of the setupWithNavController() apply:

binding.navView.selectedItemId = R.id.navigation_dashboard
Zain
  • 37,492
  • 7
  • 60
  • 84
0

By adding the setOnItemSelectedListener to your BottomNavigationView something like following should resolve the issue.

 binding.navView.setOnItemSelectedListener {
        when (it.itemId) {
            R.id.navigation_home -> {
                navController.navigate(R.id.navigation_home)
            }
            R.id.navigation_dashboard -> {
                navController.navigate(R.id.navigation_dashboard)
            }
            R.id.navigation_notifications -> {
                navController.navigate(R.id.navigation_notifications)
            }
        }
        true
    }
Nakul
  • 1,313
  • 19
  • 26
  • 1
    But this would be redundant. Clicking on the bottomnav tabs is already switching to the correct tab. I want to understand why calling navController.navigate(R.id.tab_dashboard) is messing up with the normal tap on the on tabs. Please check the video. Also this messes up with backStack queue. – Hisham Muneer Jun 04 '23 at 05:05