kotlin JetpackCompose导航嵌套图形导致“ViewModelStore应在setGraph调用之前设置”异常

pdtvr36n  于 2022-11-16  发布在  Kotlin
关注(0)|答案(6)|浏览(174)

我正在尝试将Jetpack Compose导航应用到我的应用程序中。
我的屏幕:登录/注册屏幕和底部导航栏屏幕(呼叫、聊天、设置)。
我已经发现,最好的方法是使用嵌套图。
但是我一直得到ViewModelStore should be set before setGraph call异常。但是,我认为这不是正确的异常。
我的导航已经是最新版本了。可能是我的嵌套图逻辑不正确。
要求:我希望能够从登录或注册屏幕导航到任何BottomBar屏幕并反向

@Composable
fun SetupNavGraph(
    navController: NavHostController,
    userViewModel: UserViewModel
) {
    NavHost(
        navController = navController,
        startDestination = BOTTOM_BAR_GRAPH_ROUTE,
        route = ROOT_GRAPH_ROUTE
    ) {
        loginNavGraph(navController = navController, userViewModel)
        bottomBarNavGraph(navController = navController, userViewModel)
    }
}

NavGraph.kt

fun NavGraphBuilder.loginNavGraph(
    navController: NavHostController,
    userViewModel: UserViewModel
) {
    navigation(
        startDestination = Screen.LoginScreen.route,
        route = LOGIN_GRAPH_ROUTE
    ) {
        composable(
            route = Screen.LoginScreen.route,
            content = {
                LoginScreen(
                    navController = navController,
                    loginViewModel = userViewModel
                )
            })
        composable(
            route = Screen.RegisterScreen.route,
            content = {
                RegisterScreen(
                    navController = navController,
                    loginViewModel = userViewModel
                )
            })
    }
}

LoginNavGraph.kt

fun NavGraphBuilder.bottomBarNavGraph(
    navController: NavHostController,
    userViewModel: UserViewModel
) {
    navigation(
        startDestination = Screen.AppScaffold.route,
        route = BOTTOM_BAR_GRAPH_ROUTE
    ) {
        composable(
            route = Screen.AppScaffold.route,
            content = {
                AppScaffold(
                    navController = navController,
                    userViewModel = userViewModel
                )
            })
    }
}

BottomBarNavGraph.kt

@Composable
fun AppScaffold(
    navController: NavHostController,
    userViewModel: UserViewModel
) {
    val scaffoldState = rememberScaffoldState()

    Scaffold(

        bottomBar = {
            BottomBar(mainNavController = navController)
        },
        scaffoldState = scaffoldState,

        ) {

        NavHost(
            navController = navController,
            startDestination = NavigationScreen.EmergencyCallScreen.route
        ) {
            composable(NavigationScreen.EmergencyCallScreen.route) {
                EmergencyCallScreen(
                    navController = navController,
                    loginViewModel = userViewModel
                )
            }
            composable(NavigationScreen.ChatScreen.route) { ChatScreen() }
            composable(NavigationScreen.SettingsScreen.route) {
                SettingsScreen(
                    navController = navController,
                    loginViewModel = userViewModel
                )
            }
        }
    }
}

AppScaffold.kt

@Composable
fun BottomBar(mainNavController: NavHostController) {

    val items = listOf(
        NavigationScreen.EmergencyCallScreen,
        NavigationScreen.ChatScreen,
        NavigationScreen.SettingsScreen,
    )

    BottomNavigation(
        elevation = 5.dp,
    ) {
        val navBackStackEntry by mainNavController.currentBackStackEntryAsState()
        val currentRoute = navBackStackEntry?.destination?.route
        items.map {
            BottomNavigationItem(
                icon = {
                    Icon(
                        painter = painterResource(id = it.icon),
                        contentDescription = it.title
                    )
                },
                label = {
                    Text(
                        text = it.title
                    )
                },
                selected = currentRoute == it.route,
                selectedContentColor = Color.White,
                unselectedContentColor = Color.White.copy(alpha = 0.4f),
                onClick = {
                    mainNavController.navigate(it.route) {
                        mainNavController.graph.startDestinationRoute?.let { route ->
                            popUpTo(route) {
                                saveState = true
                            }
                        }
                        restoreState = true
                        launchSingleTop = true
                    }
                },

                )
        }

    }
}

BottomBar.kt

const val ROOT_GRAPH_ROUTE = "root"
const val LOGIN_GRAPH_ROUTE = "login_register"
const val BOTTOM_BAR_GRAPH_ROUTE = "bottom_bar"

sealed class Screen(val route: String) {
    object LoginScreen : Screen("login_screen")
    object RegisterScreen : Screen("register_screen")
    object AppScaffold : Screen("app_scaffold")

}

Screen.kt

sealed class NavigationScreen(val route: String, val title: String, @DrawableRes val icon: Int) {
    object EmergencyCallScreen : NavigationScreen(
        route = "emergency_call_screen",
        title = "Emergency Call",
        icon = R.drawable.ic_phone
    )

    object ChatScreen :
        NavigationScreen(
            route = "chat_screen",
            title = "Chat",
            icon = R.drawable.ic_chat)

    object SettingsScreen : NavigationScreen(
        route = "settings_screen",
        title = "Settings",
        icon = R.drawable.ic_settings
    )
}

NavigationScreen.kt

bbuxkriu

bbuxkriu1#

在这个问题上挣扎了一段时间后,我通过使用两个独立的NavHost解决了这个问题。这可能不是正确的方法,但目前它是有效的。你可以在这里找到示例源代码:
https://github.com/talhaoz/JetPackCompose-LoginAndBottomBar
希望他们在即将发布的版本中使导航更容易。

dffbzjpn

dffbzjpn2#

不允许嵌套NavHost。这会导致ViewModelStore应在setGraph调用异常之前设置。通常,底部导航位于NavHost之外,这是docs显示的内容。推荐的方法是使用单个NavHost,您可以在其中根据您所在的目标隐藏和显示底部导航。

qc6wkl3g

qc6wkl3g3#

一个NavHost、一个NavHostController。在AppScaffold上的巢状NavHost前面建立新的NavHostController。

e37o9pze

e37o9pze4#

在实现此通用UI模式时也有类似问题:
1.主页(带有底部导航栏),此页面由Inner nav controller托管
1.单击一个页面的一些链接
1.导航到一个新页面(包含新的Scaffold示例)。此页面由Outer nav controller托管。
通过使用2个NavHost和2个navController示例,有点 * 破解 * 了这个问题。
基本思想是使用一些msg通道来告诉outer nav controller,在我的例子中是Channel

private val _pages: Channel<String> = Channel()
var pages = _pages.receiveAsFlow()

@Composable
fun Route() {
    val navController1 = rememberNavController()
    LaunchedEffect(true) {
        pages.collect { page ->
            navController1.navigate("detail")
        }
    }

    NavHost(navController = navController1, startDestination = "home") {
        composable("home") { MainPage() }
        composable("detail") { DetailPage() }
    }
}

@Composable
fun MainPage() {
    val navController2 = rememberNavController()

    val onTabSelected = { tab: String ->
        navController2.navigate(tab) {
            popUpTo(navController2.graph.findStartDestination().id) { saveState = true }
            launchSingleTop = true
            restoreState = true
        }
    }
    Scaffold(topBar = { TopAppBar(title = { Text("Home Title") }) },
        bottomBar = {
            BottomNavigation {
                val navBackStackEntry by navController2.currentBackStackEntryAsState()
                val currentDestination = navBackStackEntry?.destination
                BottomNavigationItem(
                    selected = currentDestination?.hierarchy?.any { it.route == "tab1" } == true,
                    onClick = { onTabSelected("tab1") },
                    icon = { Icon(imageVector = Icons.Default.Favorite, "") },
                    label = { Text("tab1") }
                )
                BottomNavigationItem(
                    selected = currentDestination?.hierarchy?.any { it.route == "tab2" } == true,
                    onClick = { onTabSelected("tab2") },
                    icon = { Icon(imageVector = Icons.Default.Favorite, "") },
                    label = { Text("tab2") }
                )
                BottomNavigationItem(
                    selected = currentDestination?.hierarchy?.any { it.route == "tab3" } == true,
                    onClick = { onTabSelected("tab3") },
                    icon = { Icon(imageVector = Icons.Default.Favorite, "") },
                    label = { Text("tab3") }
                )
            }
        }
    ) { value ->
        NavHost(navController = navController2, startDestination = "tab1") {
            composable("tab1") { Home() }
            composable("tab2") { Text("tab2") }
            composable("tab3") { Text("tab3") }
        }
    }
}

class HomeViewModel: ViewModel()
@Composable
fun Home(viewModel: HomeViewModel = HomeViewModel()) {
    Button(
        onClick = {
            viewModel.viewModelScope.launch {
                _pages.send("detail")
            }
        },
        modifier = Modifier.padding(all = 16.dp)
    ) {
        Text("Home", modifier = Modifier.padding(all = 16.dp))
    }
}

@Composable
fun DetailPage() {
    Scaffold(topBar = { TopAppBar(title = { Text("Detail Title") }) }) {
        Text("Detail")
    }
}

缺点:
1.应用程序需要维护UI堆栈信息。
1.响应式布局更难科普。

ki0zmccv

ki0zmccv5#

为您的函数使用rememberNavController()

fun YourFunction(
    navController: NavHostController = rememberNavController()
)
5sxhfpxr

5sxhfpxr6#

在我的情况下,我必须创建导航控制器(为底部酒吧)与在主屏幕。

@AndroidEntryPoint
class MainActivity: ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        setContent {
            Theme {
                Surface(modifier = Modifier.fillMaxSize()) {
                    AppContainer()
                }
            }
        }
    }
}

@Composable
fun AppContainer() {
    val mainNavController = rememberNavController()
    // This was causing the issue. I moved this to HomeScreen.
    // val bottomNavController = rememberNavController()

    Box(
        modifier = Modifier.background(BackgroundColor)
    ) {
        NavGraph(mainNavController)
    }
}

@Composable
fun HomeScreen(mainNavController: NavController) {
     val bottomBarNavController = rememberNavController()
}

相关问题