I would like to have two versions of the BottomNavigationView, each with its own set of tabs. Some are shared, some not. For example, Version 1 has Fragment A, B, C, D on the bottom, version two has Fragment B, C, E, F on the bottom. I would like to inflate the view and the start destination based on the type of the user. So ideally my flow would be:
What is the best way to implement this navigation stack. There are obviously shared destinations, but there are also times when version 1 should never end up on fragments E, and F, and version 2 should never accidentally see fragments A and D.
I have tried the following code in main activity, but that works very clunky. A lot of times when the user navigates up, for a split second we show a wrong version to the user.
I'm using Kotlin with MVVM, so activity_main has the following:
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_gravity="bottom"
app:itemBackground="?attr/colorSurface"
app:labelVisibilityMode="labeled">
And in the Main activity I've added all of these clunks of code:
override fun onNavigateUp(): Boolean {
Timber.e("navigate up: ${navController?.currentDestination?.id}")
Timber.e("navigate up: ${navController?.previousBackStackEntry?.id}")
return super.onNavigateUp()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Hide the default menu options to prevent duplication.
return false
}
private fun inflateBottomNavigation(user: User) {
if (isXUser(user)) {
binding.bottomNavigationView.inflateMenu(R.menu.menu_bottom_nav_x)
navController?.navigateSafely(
R.id.home_fragment) // Set the top fragment to HomeFragment.
navController?.graph?.setStartDestination(R.id.home_fragment)
} else {
binding.bottomNavigationView.inflateMenu(R.menu.menu_bottom_nav_y)
navController?.navigateSafely(
R.id.today_fragment) // Set the top fragment to TodayFragment.
navController?.graph?.setStartDestination(R.id.today_fragment)
}
// Set up bottom navigation.
binding.bottomNavigationView.setupWithNavController(navController!!)
binding.bottomNavigationView.setOnItemSelectedListener { item ->
when (item.itemId) {
R.id.home_fragment,
R.id.today_fragment -> {
if (isXUser(user)) {
navController?.navigateSafely(R.id.home_fragment)
} else {
navController?.navigateSafely(R.id.today_fragment)
}
true
}
R.id.chat_fragment,
R.id.chat_x_fragment -> {
if (isXUser(user)) {
navController?.navigateSafely(R.id.chat_x_fragment)
} else {
navController?.navigateSafely(R.id.chat_fragment)
}
true
}
else -> {
navController?.navigateSafely(item.itemId)
true
}
}
}
}
private fun isXUser(user: User): Boolean {
return user.isDebugger == true
}
private fun setupNavigation() {
navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment_container)
as NavHostFragment
navController = navHostFragment?.navController
appBarConfiguration =
AppBarConfiguration(
setOf(
R.id.home_fragment,
R.id.today_fragment,
R.id.test_fragment,
R.id.chat_fragment,
R.id.chat_x_fragment,
R.id.progress_fragment,
R.id.points_fragment),
binding.drawerLayout)
binding.toolbar.setupWithNavController(navController!!, appBarConfiguration!!)
binding.drawerNavView.setupWithNavController(navController!!)
...
// Set up bottom navigation.
binding.bottomNavigationView.setupWithNavController(navController!!)
}
private fun setUpUserObservers() {
userViewModel.user.observe(
this,
Observer { user ->
inflateBottomNavigation(user)
}
This makes the experience very clunky, each time the user ends on the today/home fragment we re-inflate. A lot of times a wrong start destination can be shown to a user before the correct one shows. What is the best approach to structure the app, and handle switching between different types of users, without duplicating too much code? Thanks
I would suggest you to create a navGraph file in the navigation directory , if you don't know how to do that you can refer it here https://developer.android.com/guide/navigation/navigation-getting-started.
And to implement the navGraph, in the acitivy_main.xml you would have to add FragmentContainerView above the BottomNavGraph.
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
Then in the main activity we would programmatically change the startDestination of the navGraph based on the userType. And I think creating two different menu files for different would be simpler.
Here's the main activity code
class MainActivity : AppCompatActivity() {
private lateinit var navController: NavController
private lateinit var bottomNavigationView: BottomNavigationView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
val navGraph = navController.navInflater.inflate(R.navigation.nav_graph)
val userType = fetchUserType()
val startDestination = if (userType == UserType.TYPE1) {
R.id.fragmentA
} else {
R.id.fragmentF
}
navGraph.startDestination = startDestination
navController.graph = navGraph
bottomNavigationView = findViewById(R.id.bottom_navigation_view)
if (userType == UserType.TYPE1) {
bottomNavigationView.inflateMenu(R.menu.menu_version1)
} else {
bottomNavigationView.inflateMenu(R.menu.menu_version2)
}
bottomNavigationView.setupWithNavController(navController)
}