Reputation: 696
I am implementing BottomNavigationBar to my app and it not works properly. I have a condition when to show bar. This is it:
data class AppState(
val a: Int // sample
) {
// other code
private val currentDestination: NavDestination?
@Composable get() = navController.currentBackStackEntryAsState().value?.destination
private val bottomBarRoutes = bottomBarTabs.map { it.direction.route }
val shouldShowBottomBar: Boolean
@Composable get() = currentDestination?.route in bottomBarRoutes
}
And to display the bottom bar I used Scaffold with navhost:
Scaffold(
bottomBar = { if (appState.shouldShowBottomBar) AppBottomBar() }
) { padding ->
// NavHost setup
AppNavigation(
// params,
modifier = Modifier.padding(padding)
)
}
This code works correctly - navigation is shown only by condition. But at the moment of transition from screen to screen a visual bug occurs: (content of second screen is collapsing because the navigation bar appears before the navigating screen)
The problem is that the condition relies on Nav Back Stack. When changes occur in it, the navigation bar appears, although the screen has not yet destroyed according to the logs:
[Screen Lifecycle] Home Screen Started
[Action] Navigating to details Screen
[Screen Lifecycle] Details Screen Started
[Screen Lifecycle] Home Screen Destroyed
[Action] Navigation Back
[Screen Lifecycle] Showing Navigation
[Screen Lifecycle] Home Screen Started
[Screen Lifecycle] Details Screen Destroyed
1. Seen many blogs and videos, but all of them have the same bug. For a code example use this blog post
2. I looked at the code on different projects and ran them, but it still doesn't work as it should.
3. I also tried to find ways to make a screen composition complete listener but found nothing.
4. Looked at the questions on stackoverflow
I have a multi modular project so just passing navigation bar to actual screens is not a good idea as it is done in the google single module sample app
Upvotes: 2
Views: 892
Reputation: 696
I can hide screen while navigating to another destination and then show it again when navigation bar will be drawn
// Preventing screen drawing before navigation is displayed (synchronizing with screen)
var navigationDrawed by remember { mutableStateOf(false) }
DisposableEffect(appState.navController) {
val listener = NavController.OnDestinationChangedListener { _, _, _ ->
navigationDrawed = false
}
appState.navController.addOnDestinationChangedListener(listener)
onDispose {
appState.navController.removeOnDestinationChangedListener(listener)
}
}
Scaffold(
bottomBar = {
if (appState.shouldShowBottomBar) {
TherapistBottomBar(
destinations = appState.bottomBarTabs,
onNavigate = appState::navigate,
currentTab = appState.currentBottomBarTab,
modifier = Modifier
.fillMaxWidth()
.drawWithContent {
drawContent()
navigationDrawed = true
}
)
} else {
navigationDrawed = true
}
}
) { innerPadding ->
NavHost(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.then(if (navigationDrawed) { Modifier } else Modifier.alpha(0f))
)
}
Upvotes: 0
Reputation: 1108
Solved the problem by seperating NavHosts like this. But I tested it on a single-module app, so might not work on muli-module.
MainActivity
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
YourAppsTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
NavHost(
navController = navController,
startDestination = "home",
) {
composable("home") {
BottomNavScreen(navigateToDetailScreen = { navController.navigate("detail") })
}
composable("detail") {
DetailScreen()
}
}
}
}
}
}
}
BottomNavScreen
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BottomNavScreen(
navigateToDetailScreen: () -> Unit,
) {
val navController = rememberNavController()
Scaffold(
bottomBar = {
val currentRoute =
navController.currentBackStackEntryAsState().value?.destination?.route
NavigationBar(modifier = Modifier.fillMaxWidth()) {
arrayOf("home", "document").forEach { item ->
NavigationBarItem(
selected = currentRoute == item,
onClick = { navController.navigate(item) },
label = { Text(item) },
icon = {}
)
}
}
},
) {
NavHost(
navController = navController,
startDestination = "home",
modifier = Modifier.padding(it)
) {
composable("home") {
HomeScreen(onButtonClick = navigateToDetailScreen)
}
composable("document") {
DocumentScreen()
}
}
}
}
Upvotes: 0