Jetpack составляет навигацию: почему у меня неправильная иерархия пунктов назначения?

Проблема:

У меня проблема с иерархией моей навигации по созданию сообщений. Версия Compose-Navigation, которую я использую: androidx.navigation:navigation-compose:2.7.7.

Фон:

Я пытаюсь реализовать простую навигацию с нижней навигацией и тремя вкладками. На каждой вкладке имеется собственная вложенная навигация, состоящая из двух экранов: основного содержимого вкладки и экрана сведений, который повторно используется во вложенных графиках.

Основное действие с NavHost для вкладок:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            TestNavigationTheme {
                val tabsController = rememberNavController()
                Scaffold(
                    bottomBar = {
                        NavigationBar {
                            val navBackStackEntry by tabsController.currentBackStackEntryAsState()
                            val currentDestination = navBackStackEntry?.destination
                            BottomTabs.items.forEach { navigationItem ->
                                NavigationBarItem(
                                    label = { Text(navigationItem.title) },
                                    selected = currentDestination
                                        ?.hierarchy
                                        ?.any { it.route == navigationItem.route } == true,
                                    icon = { Icon(navigationItem.icon, navigationItem.title) },
                                    onClick = {
                                        tabsController.navigate(navigationItem.route) {
                                            popUpTo(tabsController.graph.findStartDestination().id) {
                                                saveState = true
                                            }
                                            launchSingleTop = true
                                            restoreState = true
                                        }
                                    }
                                )
                            }
                        }
                    }
                ) { paddingValues ->
                    NavHost(
                        navController = tabsController,
                        startDestination = BottomTabs.Feed.route,
                        modifier = Modifier.padding(paddingValues = paddingValues)
                    ) {
                        navigation(
                            route = BottomTabs.Feed.route,
                            startDestination = "feed"
                        ) {
                            composable(route = "feed") {
                                FeedScreen {
                                    tabsController.navigate("details/feed")
                                }
                            }
                            details()
                        }

                        navigation(
                            route = BottomTabs.Search.route,
                            startDestination = "search"
                        ) {
                            composable(route = "search") {
                                SearchScreen {
                                    tabsController.navigate("details/search")
                                }
                            }
                            details()
                        }
                        navigation(
                            route = BottomTabs.Favourites.route,
                            startDestination = "fav"
                        ) {
                            composable(route = "fav") {
                                FavouritesScreen {
                                    tabsController.navigate("details/fav")
                                }
                            }
                            details()
                        }
                    }
                }
            }
        }
    }

    private fun NavGraphBuilder.details() {
        composable(
            route = "details/{argument}",
            arguments = listOf(
                navArgument("argument") { type = NavType.StringType }
            )
        ) { backStackEntry ->
            DetailsScreen(
                argument = backStackEntry
                    .arguments
                    ?.getString("argument")
                    ?: "Couldn't obtain the argument"
            )
        }
    }
}

Нижние вкладки:

sealed class BottomTabs(val route: String, val title: String, val icon: ImageVector) {
    data object Feed : BottomTabs("feed-tab", "Feed", Icons.Default.Home)
    data object Search : BottomTabs("search-tab", "Search", Icons.Default.Search)
    data object Favourites : BottomTabs("fav-tab", "Favourites", Icons.Default.Favorite)

    companion object {
        val items = listOf(Feed, Search, Favourites)
    }

}

Экраны вкладок:

@Composable
fun FeedScreen(navigateToDetails: () -> Unit) {
    Column(
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier.fillMaxSize()
    ) {
        Text("Hi, I'm Feed screen")
        Spacer(modifier = Modifier.size(40.dp))
        Button(onClick = navigateToDetails) {
            Text(text = "Go to details")
        }
    }
}

@Composable
fun SearchScreen(navigateToDetails: () -> Unit) {
    Column(
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier.fillMaxSize()
    ) {
        Text("Hi, I'm Search screen")
        Spacer(modifier = Modifier.size(40.dp))
        Button(onClick = navigateToDetails) {
            Text(text = "Go to details")
        }
    }
}

@Composable
fun FavouritesScreen(navigateToDetails: () -> Unit) {
    Column(
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier.fillMaxSize()
    ) {
        Text("Hi, I'm Favourites screen")
        Spacer(modifier = Modifier.size(40.dp))
        Button(onClick = navigateToDetails) {
            Text(text = "Go to details")
        }
    }
}

Экран подробностей:

@Composable
fun DetailsScreen(argument: String) {
    Column(
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier.fillMaxSize()
    ) {
        Text("I'm details screen")
        Text("Your argument is: $argument")
    }
}

Проблема:

Вы можете найти этот код внутри NavigationBarItem, который я взял из Руководства Google по навигации:

selected = currentDestination
            ?.hierarchy
            ?.any { it.route == navigationItem.route } == true

Когда я переключаюсь на третью вкладку («fav-tab») и нажимаю кнопку, чтобы открыть экран подробностей, экран подробностей открывается правильно на вкладке fav. Однако на панели навигации выделена выбранная вкладка ленты вместо вкладки избранного.

Что я пробовал:

Я попытался отладить цикл рекомпозиции навигации, поместив точку останова в NavigationBarItem. Вот иерархия:

Я не понимаю, почему feed-tab оказывается корнем иерархии. На мой взгляд, должно быть:

"details/{argument}"
"fav-tab"

потому что я перешел на экран сведений с fav-tab, а не с feed-tab.

Что я делаю не так в своей реализации? Любые мысли и советы будут очень признательны!

Видите ли вы ту же проблему при обновлении до версии Navigation 2.8.0-beta04?

ianhanniballake 07.07.2024 16:38

@ianhanniballake да, он ведет себя так же

sunakulto 07.07.2024 16:54
1
2
68
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Причиной проблемы был маршрут экрана сведений. Это было одинаково для всех трех навигационных графиков.

Согласно документации, «Route однозначно идентифицирует пункт назначения и все необходимые для него данные».

Итак, я добавил префиксы в составные детали(), и теперь все работает нормально.

Другие вопросы по теме