Injent
Injent

Reputation: 696

Conditional Bottom Navigation appears faster than the navigating screen | Jetpack Compose

Still finding better solutions!

Details

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)

enter image description here

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

What did I try

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

Take Note

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

Answers (2)

Injent
Injent

Reputation: 696

Looking for better solution

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

ObscureCookie
ObscureCookie

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.

NavHost structure

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

Related Questions