I've got the below code which has 3 tabs. The last tab has a button which navigates away from the tab view into its own view. This isolated view has a button which navigates back to the tab view and also selects a different tab.
When I press this "Navigate to Chats Page" button, the view goes back to the Settings tab, then the selected tab moves to the Chats tab as expected. However, when I then press the Settings tab, it still shows the Chat View. What have I done wrong here?
build.gradle implementations that need adding:
implementation("androidx.compose.ui:ui:1.0.0")
implementation("androidx.compose.material:material:1.0.0")
implementation("androidx.navigation:navigation-compose:2.4.0-alpha01")
MainActivity code:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
MyApplicationTheme {
TabView()
}
}
}
}
enum class TabName(
val route: String,
val icon: ImageVector,
val tabName: String,
) {
home(route = "home", icon = Icons.Rounded.Home, tabName = "Home"),
chats(route = "chats", icon = Icons.Rounded.Call, tabName = "Chats"),
settings(route = "settings", icon = Icons.Rounded.Settings, tabName = "Settings")
}
class TabViewModel: ViewModel() {
private var _selectedTab: MutableStateFlow<TabName> = MutableStateFlow(TabName.home)
val selectedTab: StateFlow<TabName> = _selectedTab.asStateFlow()
fun updateSelectedTab(selectedTab: TabName) {
_selectedTab.update { selectedTab }
}
}
enum class SettingsNavigation(
val route: String,
val icon: ImageVector,
val navigationBarTitle: String
) {
settingsHome(route = "settingsHome", icon = Icons.Rounded.Home, navigationBarTitle = "Settings"),
contactUs(route = "contactUs", icon = Icons.Rounded.Home, navigationBarTitle = "Contact Us"),
}
@Composable
fun BottomTabBarButtonsView(
navController: NavController,
tabViewModel: TabViewModel
) {
val tabs = listOf(
TabName.home,
TabName.chats,
TabName.settings
)
val selectedTab by tabViewModel.selectedTab.collectAsStateWithLifecycle()
BottomNavigation(
backgroundColor = Color.White,
contentColor = Color.Black
) {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
tabs.forEach { tab ->
BottomNavigationItem(
icon = {
Box {
Icon(
imageVector = tab.icon,
contentDescription = null,
tint = if (selectedTab == tab) {
Color.Black
} else {
Color.LightGray
}
)
}
},
label = {
Text(
text = tab.tabName,
fontSize = 10.sp,
color = if (selectedTab == tab) {
Color.Black
} else {
Color.LightGray
}
)
},
selectedContentColor = Color.Black,
unselectedContentColor = Color.Black.copy(0.4f),
alwaysShowLabel = true,
selected = tab == selectedTab,
onClick = {
tabViewModel.updateSelectedTab(tab)
navController.navigate(tab.route) {
navController.graph.startDestinationRoute?.let { route ->
popUpTo(route) {
saveState = true
}
}
launchSingleTop = true
restoreState = true
}
}
)
}
}
}
@Composable
fun TabView(
tabViewModel: TabViewModel = viewModel(),
navController: NavHostController = rememberNavController()
) {
NavHost(
navController = navController,
startDestination = TabName.home.route
) {
composable(
route = TabName.home.route,
) {
Scaffold(
bottomBar = {
BottomTabBarButtonsView(
navController = navController,
tabViewModel = tabViewModel
)
},
content = { padding ->
Box(modifier = Modifier.padding(padding)) {
HomeView()
}
}
)
}
composable(
route = TabName.chats.route,
) {
Scaffold(
bottomBar = {
BottomTabBarButtonsView(
navController = navController,
tabViewModel = tabViewModel
)
},
content = { padding ->
Box(modifier = Modifier.padding(padding)) {
ChatsView()
}
}
)
}
navigation(
startDestination = SettingsNavigation.settingsHome.route,
route = TabName.settings.route,
) {
composable(
route = SettingsNavigation.settingsHome.route,
exitTransition = {
when (targetState.destination.route) {
SettingsNavigation.settingsHome.route ->
slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Left, animationSpec = tween(500))
else -> null
}
},
) {
androidx.compose.material.Scaffold(
bottomBar = {
BottomTabBarButtonsView(
navController = navController,
tabViewModel = tabViewModel,
)
},
content = { padding ->
Box(modifier = Modifier.padding(padding)) {
SettingsView(
navController = navController
)
}
}
)
}
composable(
route = SettingsNavigation.contactUs.route,
enterTransition = {
when (initialState.destination.route) {
SettingsNavigation.settingsHome.route ->
slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Left, animationSpec = tween(500))
SettingsNavigation.contactUs.route ->
slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Right, animationSpec = tween(500))
else -> null
}
},
exitTransition = {
when (targetState.destination.route) {
SettingsNavigation.settingsHome.route ->
slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Right, animationSpec = tween(500))
SettingsNavigation.contactUs.route ->
slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Left, animationSpec = tween(500))
else -> null
}
},
) {
ContactUsView(
tabViewModel = tabViewModel,
navController = navController
)
}
}
}
}
@Composable
fun HomeView() {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
Text("Home View")
}
}
@Composable
fun ChatsView() {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
Text("Chats View")
}
}
@Composable
fun SettingsView(
navController: NavController
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
Column {
Text("Settings View")
Button(
onClick = {
navController.navigate(
SettingsNavigation.contactUs.route
)
}
) {
Text("Navigate to Contact Us Page")
}
}
}
}
@Composable
fun ContactUsView(
tabViewModel: TabViewModel,
navController: NavController
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
Column {
Text("Contact Us View")
Button(
onClick = {
GlobalScope.launch(Dispatchers.Main) {
navController.navigate(
SettingsNavigation.settingsHome.route
)
}
Timer(
"",
false
).schedule(1500) {
tabViewModel.updateSelectedTab(TabName.chats)
GlobalScope.launch(Dispatchers.Main) {
navController.navigate(
TabName.chats.route
)
}
}
}
) {
Text("Navigate to Chats Page")
}
}
}
}
Answer to this was to modify the button click inside ContactUsView
to clear the navigation history before moving to ChatsView
:
Button(
onClick = {
// Clear the settings stack
navController.navigate(TabName.chats.route) {
popUpTo(TabName.settings.route) { inclusive = true }
// inclusive = true removes settings navigation from backstack
}
// Update the selected tab
tabViewModel.updateSelectedTab(TabName.chats)
}
) {
Text("Navigate to Chats Page")
}