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