Is there a proper example of type-safe navigation with nested graphs where one of the graphs includes a NavigationBar with its own screens, and an auth/splash/onboarding screen is removed from the back stack using inclusive = true, all without duplicating rememberNavController/NavHost?
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
IncomeTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
NavigationHost()
}
}
}
}
}
@Serializable
sealed class Graph {
@Serializable
object OnboardingGraph : Graph()
@Serializable
object MainGraph : Graph()
}
@Serializable
sealed class OnboardingGraph {
@Serializable
object OnboardingScreen : OnboardingGraph()
}
@Serializable
sealed class MainGraph {
@Serializable
object Home : MainGraph()
@Serializable
object Notification : MainGraph()
@Serializable
object History : MainGraph()
@Serializable
object Settings : MainGraph()
}
enum class TopLevelRoute(
val route: Any,
@StringRes val label: Int,
@DrawableRes val iconFill: Int
) {
HOME(
MainGraph.Home,
R.string.tab_home,
R.drawable.baseline_home_24
),
NOTIFICATION(
MainGraph.Notification,
R.string.tab_notification,
R.drawable.baseline_notifications_24
),
HISTORY(
MainGraph.History,
R.string.tab_history,
R.drawable.baseline_history_edu_24
),
SETTINGS(
MainGraph.Settings,
R.string.tab_settings,
R.drawable.baseline_settings_24
);
}
@Composable
fun BottomNavigationBar(
navController: NavHostController,
currentDestination: NavDestination?
) {
NavigationBar {
TopLevelRoute.entries.forEach { navigationItem ->
NavigationBarItem(
selected = currentDestination?.hierarchy?.any {
it.hasRoute(navigationItem.route::class)
} == true,
label = {
Text(text = stringResource(navigationItem.label))
},
icon = {
Icon(
painterResource(navigationItem.iconFill),
contentDescription = stringResource(navigationItem.label)
)
},
onClick = {
navController.navigate(navigationItem.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
)
}
}
}
@Composable
private fun NavigationHost() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = Graph.OnboardingGraph,
enterTransition = { EnterTransition.None },
exitTransition = { ExitTransition.None }
) {
onboardingNavGraph(navController)
composable<MainGraph.Home> {
MainScreenUI(navController = navController)
}
}
}
fun NavGraphBuilder.onboardingNavGraph(
navController: NavController
) {
navigation<Graph.OnboardingGraph>(startDestination = OnboardingGraph.OnboardingScreen) {
composable<OnboardingGraph.OnboardingScreen> {
OnboardingScreen(
onFinished = {
navController.navigate(MainGraph.Home) {
popUpTo(OnboardingGraph.OnboardingScreen) {
inclusive = true
}
}
}
)
}
}
}
@Composable
fun MainScreenUI(
navController: NavHostController
) {
val navController = rememberNavController()
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
Scaffold(
modifier = Modifier.fillMaxSize(),
bottomBar = {
BottomNavigationBar(
navController = navController,
currentDestination = currentDestination
)
}
) { paddingValues ->
NavHost(
modifier = Modifier.padding(paddingValues),
navController = navController,
startDestination = Graph.MainGraph,
enterTransition = { EnterTransition.None },
exitTransition = { ExitTransition.None }
) {
mainNavGraph(navController)
}
}
}
fun NavGraphBuilder.mainNavGraph(
navController: NavController
) {
navigation<Graph.MainGraph>(startDestination = MainGraph.Home) {
composable<MainGraph.Home> {
HomeScreen { /* TODO */ }
}
composable<MainGraph.Notification> {
NotificationScreen { /* TODO */ }
}
composable<MainGraph.History> {
HistoryScreen { /* TODO */ }
}
composable<MainGraph.Settings> {
SettingsScreen { /* TODO */ }
}
}
}
Also tried using a single NavHost with two nested graphs, but calling:
navController.navigate(MainGraph.Home) {
popUpTo(OnboardingGraph.OnboardingScreen) {
inclusive = true
}
}
results in the error:
"Ignoring popBackStack to destination XXXXXX as it was not found on the current back stack",
as well as screen flickering when navigating to the same screen again. The error occurs because, when tapping on a bottom navigation item, the popUpTo method is called with the start destination ID, which is determined using navController.graph.findStartDestination().id. However, the start destination of the graph remains the onboarding screen, which was already removed from the back stack after it was completed. As a result, an attempt is made to "pop up" to a screen that no longer exists in the stack. (In the case of attempting to use two graph builders with a single NavHost) At the moment, I see a solution by setting the MainGraph\Home screen as the destination in the bottom navigation instead of using findStartDestination()
. However, I'm not sure if this is the best practice.
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
IncomeTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
MainScreenUI()
}
}
}
}
}
@Serializable
sealed class Graph {
@Serializable
object OnboardingGraph : Graph()
@Serializable
object MainGraph : Graph()
}
@Serializable
sealed class OnboardingGraph {
@Serializable
object OnboardingScreen : OnboardingGraph()
}
@Serializable
sealed class MainGraph {
@Serializable
object Home : MainGraph()
@Serializable
object Notification : MainGraph()
@Serializable
object History : MainGraph()
@Serializable
object Settings : MainGraph()
}
enum class TopLevelRoute(
val route: Any,
@StringRes val label: Int,
@DrawableRes val iconFill: Int
) {
HOME(
MainGraph.Home,
R.string.tab_home,
R.drawable.baseline_home_24
),
NOTIFICATION(
MainGraph.Notification,
R.string.tab_notification,
R.drawable.baseline_notifications_24
),
HISTORY(
MainGraph.History,
R.string.tab_history,
R.drawable.baseline_history_edu_24
),
SETTINGS(
MainGraph.Settings,
R.string.tab_settings,
R.drawable.baseline_settings_24
);
}
@Composable
fun MainScreenUI() {
val navController = rememberNavController()
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
Scaffold(
modifier = Modifier.fillMaxSize(),
bottomBar = {
// distance check to exclude non-incoming screens
BottomNavigationBar(
navController = navController,
currentDestination = currentDestination
)
}
) { paddingValues ->
NavHost(
modifier = Modifier.padding(paddingValues),
navController = navController,
startDestination = Graph.OnboardingGraph,
enterTransition = { EnterTransition.None },
exitTransition = { ExitTransition.None }
) {
onboardingNavGraph(navController)
mainNavGraph(navController)
}
}
}
@Composable
fun BottomNavigationBar(
navController: NavHostController,
currentDestination: NavDestination?
) {
NavigationBar {
TopLevelRoute.entries.forEach { navigationItem ->
NavigationBarItem(
selected = currentDestination?.hierarchy?.any {
it.hasRoute(navigationItem.route::class)
} == true,
label = {
Text(text = stringResource(navigationItem.label))
},
icon = {
Icon(
painterResource(navigationItem.iconFill),
contentDescription = stringResource(navigationItem.label)
)
},
onClick = {
navController.navigate(navigationItem.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
)
}
}
}
fun NavGraphBuilder.onboardingNavGraph(
navController: NavController
) {
navigation<Graph.OnboardingGraph>(startDestination = OnboardingGraph.OnboardingScreen) {
composable<OnboardingGraph.OnboardingScreen> {
OnboardingScreen(
onFinished = {
navController.navigate(MainGraph.Home) {
popUpTo(OnboardingGraph.OnboardingScreen) {
inclusive = true
}
}
}
)
}
}
}
fun NavGraphBuilder.mainNavGraph(
navController: NavController
) {
navigation<Graph.MainGraph>(startDestination = MainGraph.Home) {
composable<MainGraph.Home> {
HomeScreen { /* TODO */ }
}
composable<MainGraph.Notification> {
NotificationScreen { /* TODO */ }
}
composable<MainGraph.History> {
HistoryScreen { /* TODO */ }
}
composable<MainGraph.Settings> {
SettingsScreen { /* TODO */ }
}
}
}
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744735608a4590726.html
评论列表(0条)