Reputation: 51
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.
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.
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?
}
}
Upvotes: 5
Views: 5066
Reputation: 14342
You should do it as follows -
In your MainActivity, call MyBottomNavApp
class MainActivity : ComponentActivity() {
lateinit var navController: NavController
private val viewModel: MyAppViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
installSplashScreen().apply {
setKeepOnScreenCondition {
viewModel.isLoading.value
}
}
setContent {
UnicornTheme {
// Uncomment below and MyApp lines to use drawer navigation
navController = rememberNavController()
// MyApp(navController)
// Uncomment MyBottomNavApp to get bottomnav navigation
MyBottomNavApp(navController as NavHostController)
}
}
}
override fun onBackPressed() {
super.onBackPressed()
}
}
Then, implement MyBottomNavApp with a NavHost as below
@Composable
fun MyBottomNavApp(
navController: NavHostController
) {
Scaffold(
modifier = Modifier.navigationBarsPadding(),
bottomBar = { MyBottomNavigation(navController = navController)},
) { innerPadding ->
Box(Modifier.padding(innerPadding)) {
NavHost(
navController = navController,
startDestination = Home.route
) {
composable(Home.route) {
DashboardScreen(navController = navController)
}
composable(Settings.route) {
NavigationScreen(navController = navController)
}
}
}
}
}
Then, implement Destinations as below -
interface Destinations {
val route: String
val icon: ImageVector
val title: String
}
object Home: Destinations {
override val route = "Dashboard"
override val icon = Icons.Filled.Dashboard
override val title = "Dashboard"
}
object Settings: Destinations {
override val route = "Navigation"
override val icon = Icons.Filled.Navigation
override val title = "Navigation"
}
Then, implement MyBottomNavigation as below -
@Composable
fun MyBottomNavigation(
navController: NavController
) {
val destinationList = listOf<Destinations> (
Dashboard,
Navigation
)
val selectedIndex = rememberSaveable {
mutableStateOf(0)
}
BottomNavigation(
backgroundColor = colorResource(id = R.color.primary_contrast),
contentColor = colorResource(id = R.color.secondary_contrast)
) {
destinationList.forEachIndexed{index, destination ->
BottomNavigationItem(
label = { Text(text = destination.title)},
icon = { Icon(imageVector = destination.icon, contentDescription = destination.title)},
selected = index == selectedIndex.value,
onClick = {
selectedIndex.value = index
navController.navigate(destinationList[index].route) {
popUpTo(Home.route)
launchSingleTop = true
}
}
)
}
}
}
You can see an example of this in the feature/develop_starter_kit_bottom_navigation branch of the following repo -
https://github.com/arunabhdas/unicorn-commerce/tree/feature/develop_starter_kit_bottom_navigation
Upvotes: 0
Reputation: 2709
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.
Upvotes: 1