androidandroid-jetpackandroid-jetpack-composeandroid-jetpack-navigationandroid-jetpack-compose-scaffold

Jetpack Compose - Navigation - Scaffold + NavHost not working


so I am trying to create an app with Jetpack Compose. I have a Screen function which contains a Scaffold with no top app bar, a bottom bar for navigation and a floating action button set into the bottom bar. This all works fine.

However, when I add a NavHost to the scaffold's content, the whole thing stops working. It all works fine without a NavHost, and simply just the content being the composable function for a screen. I have tried with differing amounts of composable locations for the NavHost, different values for padding all to no avail.

What it looks like without a NavHost (i.e. how I want it to look)

Code:

sealed class Screen(val route: String, @DrawableRes val iconId: Int){
    object Home         : Screen("home", R.drawable.ic_home_24px)
    object Stats        : Screen("stats", R.drawable.ic_stats_icon)
    object Add          : Screen("add", R.drawable.ic_add_24px)
    object Programs     : Screen("programs", R.drawable.ic_programs_icon)
    object Exercises    : Screen("exercises", R.drawable.ic_exercises_icon)
}

@ExperimentalFoundationApi
@Preview
@Composable
fun Screen(){
    val navController = rememberNavController()
    Scaffold(
        backgroundColor = OffWhite,
        bottomBar = {
            BottomBar(navController = navController)
        },
        floatingActionButton = {
            FloatingActionButton(
                onClick = {},
                shape = CircleShape,
                backgroundColor = Blue
            ) {
                Icon(
                    painter = painterResource(id = R.drawable.ic_add_24px),
                    contentDescription = "Add",
                    tint = OffWhite,
                    modifier = Modifier
                        .padding(12.dp)
                        .size(32.dp)
                )
            }
        },
        isFloatingActionButtonDocked = true,
        floatingActionButtonPosition = FabPosition.Center,

    ) {
        HomeScreen()
//        NavHost(
//            navController = navController,
//            startDestination = Screen.Home.route
//        ){
//            composable(Screen.Home.route){ HomeScreen() }
//            composable(Screen.Stats.route){ HomeScreen() }
//            composable(Screen.Programs.route){ HomeScreen() }
//            composable(Screen.Exercises.route){ HomeScreen() }
//        }
    }
}


@Composable
fun BottomBar(
    navController : NavController
){
    val items = listOf(
        Screen.Home,
        Screen.Stats,
        Screen.Add,
        Screen.Programs,
        Screen.Exercises
    )
    BottomAppBar(
        backgroundColor = OffWhite,
        cutoutShape = CircleShape,
        content = {
            BottomNavigation(
                backgroundColor = OffWhite,
                contentColor = OffWhite,
                modifier = Modifier
                    .height(100.dp)
            ) {
                val navBackStackEntry by navController.currentBackStackEntryAsState()
                val currentDestination = navBackStackEntry?.destination
                items.forEach { screen ->
                    val selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true
                    BottomNavigationItem(
                        icon = {
                            val iconSize = if (selected) 32.dp else 20.dp
                            Icon(
                                painter = painterResource(id = screen.iconId),
                                contentDescription = screen.route,
                                tint = Blue,
                                modifier = Modifier
                                    .padding(12.dp)
                                    .size(iconSize)
                            )
                        },
                        selected = selected,
                        onClick = {
                            //Navigate to selected screen
                            navController.navigate(screen.route) {
                                //Pop all from stack
                                popUpTo(navController.graph.findStartDestination().id){
                                    saveState = true
                                }
                                //Avoid multiple copies of same screen on stack
                                launchSingleTop = true
                                //Restore state when reselecting a previously selected item
                                restoreState = true
                            }
                        },
                        alwaysShowLabel = false
                    )
                }
            }
        }
    )
}

What it looks like with the NavHost. The boxes in that image are the BottomBar failing to draw, as each box can be clicked on which takes me to BottomBar, BottomNavigationItem, Icon etc. Anyone have any idea whats going on here, and what I can do to fix it? Thanks

Edit: one thing I thought of was changing the 'selected' boolean value in fun BottomBar -> BottomNavigationItem to always be true, just to see if null values were affecting it but this did not change anything.


Solution

  • Error Message says Preview does not suppport ViewModels creation. Since, NavHost create viewmodels, does the error. So what I did to somewhat preview the scaffold and some screen, I separate the content of the scaffold.

    Example:

    @Composable
    fun MainApp(
        navController: NavController,
        content: @Composable (PaddingValues) -> Unit
    ) {
        StoreTheme {
            Scaffold(
                bottomBar = { BottomAppNavigationBar(navController) },
                content = content
            )
        }
    }
    

    On Real App:

    ...
    
    val navController = rememberNavController()
    MainApp(navController) { innerPadding ->
        NavHost(
            navController = navController,
            startDestination = BottomNavMenu.Screen1.route,
            modifier = Modifier.padding(innerPadding)
        ) {
            composable(...) { Screen1... }
            composable(...) { Screen2... }
        }
    }
    ...
    

    On Preview:

    @Preview(showBackground = true)
    @Composable
    fun MainAppPreview() {
        val navController = rememberNavController()
        
        MainApp(navController) {
            Screen1(navController)
        }
    }