Problem with Jetpack Compose Navigation in BottomNavigationBar

Viewed 2611

Based on the following image, my App currently contains 2 main routes : Auth and Dashboard. When the user started the App, an observer will check whether the user is logged in and direct he/she to the Login Page/Dashboard.

Navigation Flow

From the image as you can see, I've set the NavGraph(which include all the Dashboard routes) in the MainActivity. The problem is when user click one of the bottom navigation items, he/she will be directed straight to the view page. Instead, what I wanted is to show the user the view inside the scaffold content of the Dashboard. Check the image below.

Problem Encountered

Based on the info, how can I get the composable view inside the scaffold content once the user click the item?

Any guidance will be much appreciated. Thanks!

Side Note: This is my first compose project and I am trying to implement the Single Activity architecture without any fragment used. I apologize prior if my code violate any design or programming convention as I am quite new into programming[self-learner aka Hobbyist here :( ].

MainActivity.kt

package com.example.firstcomposeproject

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navigation
import com.example.firstcomposeproject.authview.FirebaseUserViewModel
import com.example.firstcomposeproject.authview.LogIn
import com.example.firstcomposeproject.authview.SignUpFragment
import com.example.firstcomposeproject.dashboard.*

import com.example.firstcomposeproject.ui.theme.FirstComposeProjectTheme


class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val userViewModel: FirebaseUserViewModel by viewModels()

        setContent {
            FirstComposeProjectTheme(
                darkTheme = false
            ) {
                val navController = rememberNavController()


                /*
                Observing FirebaseUser LiveData to check if user is logged in. If yes, direct user
                to dashboard, else direct user to authenticate screen.
                */
                userViewModel.userMutableLiveData.observe(this, {
                        firebaseUser ->
                    if(firebaseUser != null){
                        navController.popBackStack()
                        navController.navigate("dashboard"){
                            popUpTo("auth"){
                                inclusive = true
                            }
                        }

                        userViewModel.clearFormField()
                    }else {
                        navController.navigate("auth"){
                            popUpTo("dashboard"){
                                inclusive = true
                            }
                        }
                    }
                })


                //Navigation Graph For The Whole App
                NavHost(navController = navController, startDestination = "auth"){
                    navigation(startDestination = "logInUI", route = "auth"){
                        composable("logInUI"){
                            LogIn(
                                userViewModel,
                                onNavigate = {
                                    navigateTo(it, navController)
                                }
                            ).FinalView()
                        }

                        composable("signUpUI"){
                            SignUpFragment(
                                userViewModel,
                                onNavigate = {
                                    navigateTo(it, navController)
                                }
                            ).FinalView()
                        }
                    }
                    addDashBoardGraph(userViewModel, navController)
                }
            }
        }
    }

    private fun navigateTo(
        dest: String,
        navController: NavController
    ){
        navController.navigate(dest)
    }



}

Dashboard.kt

package com.example.firstcomposeproject.dashboard

import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.navigation
import com.example.firstcomposeproject.authview.FirebaseUserViewModel
import com.example.firstcomposeproject.dashboard.helper.Screen

import com.example.firstcomposeproject.ui.theme.Teal200

fun NavGraphBuilder.addDashBoardGraph(
    userViewModel: FirebaseUserViewModel,
    navController: NavHostController
  ) {
    navigation(startDestination = "dashboardUI", route = "dashboard"){
        composable("dashboardUI"){
            Dashboard(
                navController,
                userViewModel
            )
        }

        composable("navigationUI"){
            Navigation()
        }

        composable("messageUI"){
            Message()
        }

        composable("historyUI"){
            History()
        }

        composable("profileUI"){
            Profile(
                userViewModel
            )
        }
    }
}


@Composable
fun Dashboard(
    navController: NavHostController,
    userViewModel: FirebaseUserViewModel
){
    val items = listOf(
        Screen.Navigation,
        Screen.Message,
        Screen.History,
        Screen.Profile
    )

    Scaffold(
        bottomBar = {
            BottomNavigation(
                backgroundColor = Color.White,
                contentColor = Teal200
            ) {
                val navBackStackEntry by navController.currentBackStackEntryAsState()
                val currentDestination = navBackStackEntry?.destination

                items.forEach{ screen ->
                    BottomNavigationItem(
                        label = { Text(text = screen.label)},
                        selected = currentDestination?.hierarchy?.any{ it.route == screen.route } == true,
                        onClick = {
                            navController.navigate(screen.route)
                            {

                                popUpTo(navController.graph.findStartDestination().id) {
                                    saveState = true
                                }
                                launchSingleTop = true

                                restoreState = true
                            }
                        },
                        icon = {
                            Icon(painterResource(id = screen.icon), screen.label)
                        }, unselectedContentColor = Color.Gray
                    )
                }

            }
        }
    ){
        //Scaffold Content - How to access the compose function from the MainActivity NavGraph here?
     }
}
1 Answers

Looking at your code, I think you need to set a NavHost in your Scaffold content in the Dashboard function, to tell the system you want to switch composables inside the layout with the bottomBar you created.

I see you already understand about nested navigation, so if you just want to reuse the same NavHost you can try to define it in a specific composable function.

@Composable
fun MyNavGraph(
    navController: NavHostController = rememberNavController(),
    userViewModel: FirebaseUserViewModel
){
    NavHost(navController = navController, startDestination = "auth"){
        navigation(startDestination = "logInUI", route = "auth"){
            composable("logInUI"){
                LogIn(
                    userViewModel,
                    onNavigate = {
                        navigateTo(it, navController)
                    }
                ).FinalView()
            }

            composable("signUpUI"){
                SignUpFragment(
                    userViewModel,
                    onNavigate = {
                        navigateTo(it, navController)
                    }
                ).FinalView()
            }
        }
        addDashBoardGraph(userViewModel, navController)
    }
}


I've not tried it, so let me know if fit your situation. Hope it helps a little.

Related