Изменение обратного стека с помощью множественного обратного стека BottomNavigation с безопасностью типов в Jetpack CompAndroid

Форум для тех, кто программирует под Android
Ответить Пред. темаСлед. тема
Anonymous
 Изменение обратного стека с помощью множественного обратного стека BottomNavigation с безопасностью типов в Jetpack Comp

Сообщение Anonymous »

Я создал образец навигации с несколькими обратными стеками и нижней навигацией, но при нажатии кнопки «Назад» из другой вкладки или приложения навигации происходит переход к начальному месту назначения графика, а не к последнему в обратном стеке.
Как видно на gif-изображении, главный экран хранит состояния и задний стек с состояниями сохранения и восстановления, но при нажатии кнопки «Назад» он перемещается на HomeScreen1, а задний стек содержит HomeScreen3. Однако переход на другую вкладку и возврат на главную восстанавливает состояние.
Изображение

Очевидно, из-за этого
popUpTo(findStartDestination(nestedNavController.graph).id)

Как правильно извлечь или сохранить обратную стопку, по которой она перемещается, чтобы исправить страницу при обратном нажатии?
График навигацииprivate fun NavGraphBuilder.addBottomNavigationGraph(
nestedNavController: NavHostController,
onScreenClick: (route: Any, navBackStackEntry: NavBackStackEntry) -> Unit,
) {
navigation(
startDestination = BottomNavigationRoute.HomeRoute1
) {
composable { from: NavBackStackEntry ->
Screen(
text = "Home Screen1",
navController = nestedNavController,
onClick = {
nestedNavController.navigate(BottomNavigationRoute.HomeRoute2)
}
)
}

composable { from: NavBackStackEntry ->
Screen(
text = "Home Screen2",
navController = nestedNavController,
onClick = {
nestedNavController.navigate(BottomNavigationRoute.HomeRoute3)
}
)
}

composable { from: NavBackStackEntry ->
Screen(
text = "Home Screen3",
navController = nestedNavController
)
}
}

navigation(
startDestination = BottomNavigationRoute.SettingsRoute1
) {
composable { from: NavBackStackEntry ->
Screen(
text = "Settings Screen",
navController = nestedNavController,
onClick = {
nestedNavController.navigate(BottomNavigationRoute.SettingsRoute2)
}
)
}

composable { from: NavBackStackEntry ->
Screen(
text = "Settings Screen2",
navController = nestedNavController,
onClick = {
nestedNavController.navigate(BottomNavigationRoute.SettingsRoute3)
}
)
}

composable { from: NavBackStackEntry ->
Screen(
text = "Settings Screen3",
navController = nestedNavController
)
}
}

composable { from: NavBackStackEntry ->
Screen(
text = "Favorites Screen",
navController = nestedNavController,
onClick = {
onScreenClick(
Profile("Favorites"),
from
)
}
)
}

composable { from: NavBackStackEntry ->
Screen(
text = "Notifications Screen",
navController = nestedNavController,
onClick = {
onScreenClick(
Profile("Notifications"),
from
)
}
)
}
}

Корневые и вложенные компоненты NavHost
@Preview
@Composable
fun NavigationTest() {
val navController = rememberNavController()

NavHost(
modifier = Modifier.fillMaxSize(),
navController = navController,
startDestination = BottomNavigationRoute.DashboardRoute,
enterTransition = {
slideIntoContainer(
towards = SlideDirection.Start,
animationSpec = tween(700)
)
},
exitTransition = {
slideOutOfContainer(
towards = SlideDirection.End,
animationSpec = tween(700)
)
},
popEnterTransition = {
slideIntoContainer(
towards = SlideDirection.Start,
animationSpec = tween(700)
)
},
popExitTransition = {
slideOutOfContainer(
towards = SlideDirection.End,
animationSpec = tween(700)
)
}
) {

composable {
MainContainer { route: Any, navBackStackEntry: NavBackStackEntry ->
// Navigate only when life cycle is resumed for current screen
if (navBackStackEntry.lifecycleIsResumed()) {
navController.navigate(route = route)
}
}
}

composable
{ navBackStackEntry: NavBackStackEntry ->
val profile: Profile = navBackStackEntry.toRoute()
Screen(profile.toString(), navController)
}
}
}

@Composable
private fun MainContainer(
onScreenClick: (
route: Any,
navBackStackEntry: NavBackStackEntry,
) -> Unit,
) {
val items = remember {
bottomRouteDataList()
}

val nestedNavController = rememberNavController()

val navBackStackEntry: NavBackStackEntry? by nestedNavController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination

Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(
title = {
Text("TopAppbar")
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.White
)
)
},
bottomBar = {

NavigationBar(
modifier = Modifier.height(56.dp),
tonalElevation = 4.dp
) {
items.forEach { item: BottomRouteData ->

// Checks destination's route with type safety
val selected =
currentDestination?.hierarchy?.any { it.hasRoute(item.route::class) } == true

NavigationBarItem(
selected = selected,
icon = {
Icon(
imageVector = item.icon,
contentDescription = null
)
},
onClick = {

// This is for not opening same screen if current destination
// is equal to target destination
if (selected.not()) {

nestedNavController.navigate(route = item.route) {
launchSingleTop = true

// 🔥 If restoreState = true and saveState = true are commented
// routes other than Home1 are not saved
restoreState = true

// Pop up backstack to the first destination and save state.
// This makes going back
// to the start destination when pressing back in any other bottom tab.
popUpTo(findStartDestination(nestedNavController.graph).id) {
saveState = true
}
}
}
}
)
}
}
}
) { paddingValues: PaddingValues ->
NavHost(
modifier = Modifier.padding(paddingValues),
navController = nestedNavController,
startDestination = BottomNavigationRoute.HomeRoute
) {
addBottomNavigationGraph(nestedNavController) { route, navBackStackEntry ->
onScreenClick(route, navBackStackEntry)
}
}
}
}

Экраны для отображения и отслеживания текущего стека
@SuppressLint("RestrictedApi")
@Composable
private fun Screen(
text: String,
navController: NavController,
onClick: (() -> Unit)? = null,
) {

val packageName = LocalContext.current.packageName

var counter by rememberSaveable {
mutableIntStateOf(0)
}

Column(
modifier = Modifier
.background(MaterialTheme.colorScheme.surface)
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = text,
fontSize = 26.sp,
fontWeight = FontWeight.Bold
)

Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
counter++
}
) {
Text("Counter: $counter")
}

onClick?.let {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
onClick()
}
) {
Text("Navigate next screen")
}
}

val currentBackStack: List by navController.currentBackStack.collectAsState()

LazyColumn(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {

// Don't do looped operations in actual code, it's for demonstration
items(items = currentBackStack.reversed()) {
Text(
text = it.destination.route
?.replace("$packageName.", "")
?.replace(
"BottomNavigationRoute.",
""
) ?: it.destination.displayName,
modifier = Modifier
.shadow(4.dp, RoundedCornerShape(8.dp))
.background(Color.White)
.fillMaxWidth()
.padding(16.dp),
fontSize = 18.sp
)
}
}
}
}

Маршруты и данные маршрутов
@Serializable
sealed class BottomNavigationRoute {

@Serializable
data object DashboardRoute : BottomNavigationRoute()

@Serializable
data object HomeRoute : BottomNavigationRoute()

@Serializable
data object HomeRoute1 : BottomNavigationRoute()

@Serializable
data object HomeRoute2 : BottomNavigationRoute()

@Serializable
data object HomeRoute3 : BottomNavigationRoute()

@Serializable
data object SettingsRoute : BottomNavigationRoute()

@Serializable
data object SettingsRoute1 : BottomNavigationRoute()

@Serializable
data object SettingsRoute2 : BottomNavigationRoute()

@Serializable
data object SettingsRoute3 : BottomNavigationRoute()

@Serializable
data object FavoritesRoute : BottomNavigationRoute()

@Serializable
data object NotificationRoute : BottomNavigationRoute()
}

internal fun bottomRouteDataList() = listOf(
BottomRouteData(
title = "Home",
icon = Icons.Default.Home,
route = BottomNavigationRoute.HomeRoute
),
BottomRouteData(
title = "Settings",
icon = Icons.Default.Settings,
route = BottomNavigationRoute.SettingsRoute
),
BottomRouteData(
title = "Favorites",
icon = Icons.Default.Favorite,
route = BottomNavigationRoute.FavoritesRoute
),
BottomRouteData(
title = "Notifications",
icon = Icons.Default.Notifications,
route = BottomNavigationRoute.NotificationRoute
)
)

data class BottomRouteData(
val title: String,
val icon: ImageVector,
val route: BottomNavigationRoute,
)

Функции навигации от JetSnack
/**
* If the lifecycle is not resumed it means this NavBackStackEntry already processed a nav event.
*
* This is used to de-duplicate navigation events.
*/
internal fun NavBackStackEntry.lifecycleIsResumed() =
this.lifecycle.currentState == Lifecycle.State.RESUMED

private val NavGraph.startDestination: NavDestination?
get() = findNode(startDestinationId)

/**
* Copied from similar function in NavigationUI.kt
*
* https://cs.android.com/androidx/platfor ... ationUI.kt
*/
internal tailrec fun findStartDestination(graph: NavDestination): NavDestination {
return if (graph is NavGraph) findStartDestination(graph.startDestination!!) else graph
}


Подробнее здесь: https://stackoverflow.com/questions/791 ... e-safety-i
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение

Вернуться в «Android»