【问题标题】:Jetpack Compose: Bottom bar navigation not responding after deep-linkingJetpack Compose:深度链接后底栏导航没有响应
【发布时间】:2021-11-06 23:24:34
【问题描述】:

我在我的新 Jetpack Compose 应用程序中设置了一个底部栏,其中包含 2 个目的地。我已尝试遵循 Google 提供的示例。

例如,它看起来像这样:

@Composable
fun MyBottomBar(navController: NavHostController) {
    val items = listOf(
        BottomNavigationScreen.ScreenA,
        BottomNavigationScreen.ScreenB
    )
    val navBackStackEntry by navController.currentBackStackEntryAsState()
    val currentDestination = navBackStackEntry?.destination

    BottomNavigation {
        items.forEach { screen ->
            BottomNavigationItem(
                onClick = {
                    navController.navigate(screen.route) {
                        popUpTo(navController.graph.findStartDestination().id) {
                            saveState = true
                        }

                        launchSingleTop = true
                        restoreState = true

                    }
                },
                selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
                icon = { Icon(imageVector = screen.icon, contentDescription = null) },
                label = { Text(stringResource(screen.label)) }
            )
        }
    }
}

这一切都很好,我可以在两个目的地之间导航。但是,我也有一个指向 ScreenB 的深层链接。一旦被调用,按下 ScreenA 按钮似乎什么都不做(如果我添加日志记录,我可以看到 currentDestination 被重复设置为 ScreenB)但按下返回返回到 ScreenA 的 startDestination。

我目前的解决方法是从示例代码中删除 restoreState = true 行。

我的怀疑是关于深层链接的某些东西正在持续存在,尽管它试图转到 ScreenA,但导航组件说它有一个指向 ScreenB 的深层链接,所以它就去了那里。我尝试重置活动意图,使其在意图中没有标志和数据,我什至尝试更改意图操作类型,但都无济于事。

我正在使用 Compose 1.0.0-rc02 和 Compose Navigation 2.4.0-alpha04。

是我做错了什么还是这是一个错误?

【问题讨论】:

  • 你找到解决办法了吗?
  • 不。我现在正在使用 Compose 1.0.0 和 Compose Navigation 2.4.0-alpha08,但问题仍然存在。我必须在没有保存/恢复状态的情况下继续
  • 这种行为让我发疯:P 经过一些测试,我发现了一些非常奇怪的东西,我可以很容易地重现。深度链接调用后,我的“屏幕 A”菜单点变为“屏幕 B”。我的意思是,我观察到“navController.currentBackStackEntryFlow”,它为其他菜单点返回了良好的值(在我的情况下,我有 4 个),但主菜单点“损坏”并且像第二个菜单点一样。
  • 那么听起来肯定像一个错误 - 不是错误的使用。我将在深度链接之前和之后查看自己的回栈条目。你的底栏在你按下后是否开始正常工作?
  • 是的,之后backstack就可以正常工作了。

标签: deep-linking android-jetpack-compose android-architecture-navigation


【解决方案1】:

我知道您从官方文档中获得了此代码,但我认为它不适用于底部导航。它将元素保留在导航堆栈中,因此从ScreenB 按下后退按钮将带您回到ScreenA,在这种情况下,我认为这不是正确的行为。

这就是为什么最好从堆栈中删除所有元素,以便始终只留下一个选项卡。并且使用saveState,无论如何您都不会丢失状态。这可以按如下方式完成:

fun NavHostController.navigateBottomNavigationScreen(screen: BottomNavigationScreen) = navigate(screen.route) {
    val navigationRoutes = BottomNavigationScreen.values()
        .map(BottomNavigationScreen::route)
    val firstBottomBarDestination = backQueue
        .firstOrNull { navigationRoutes.contains(it.destination.route) }
        ?.destination
    if (firstBottomBarDestination != null) {
        popUpTo(firstBottomBarDestination.id) {
            inclusive = true
            saveState = true
        }
    }
    launchSingleTop = true
    restoreState = true
}

并像这样使用它:

BottomNavigationItem(
    onClick = {
        navController.navigateBottomNavigationScreen(screen)
    },
    selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
    icon = { Icon(imageVector = screen.icon, contentDescription = null) },
    label = { Text(screen.label) }
)

出于同样的原因,在这种情况下我不会使用深层链接导航。相反,您手动处理它们。如果您离开底部导航视图并返回,您可以使用视图模型不重新处理深层链接:

class DeepLinkProcessingViewModel : ViewModel() {
    private var deepLinkProcessed = false

    fun processDeepLinkIfAvailable(context: Context): String? {
        if (!deepLinkProcessed) {
            val activity = context.findActivity()
            val intentData = activity?.intent?.data?.toString()

            deepLinkProcessed = true
            return intentData
        }
        return null
    }
}

使用此视图模型,您可以像这样计算起始目的地:

val context = LocalContext.current
val deepLinkProcessingViewModel = viewModel<DeepLinkProcessingViewModel>()
val startDestination = rememberSaveable(context) {
    val deepLink = deepLinkProcessingViewModel.processDeepLinkIfAvailable(context)
    if (deepLink == "example://playground") {
        // deep link handled
        BottomNavigationScreen.ScreenB.route
    } else {
        // default start destination
        BottomNavigationScreen.ScreenA.route
    }
}
NavHost(navController = navController, startDestination = startDestination) {
    ...
}

或者,如果您在底部导航前面有多个导航元素,并且您不希望通过深层链接丢失它们,您可以按照以下方式进行操作:

val navController = rememberNavController()
val context = LocalContext.current
val deepLinkProcessingViewModel = viewModel<DeepLinkProcessingViewModel>()
LaunchedEffect(Unit) {
    val deepLink = deepLinkProcessingViewModel.processDeepLinkIfAvailable(context) ?: return@LaunchedEffect
    if (deepLink == "example://playground") {
        navController.navigateBottomNavigationScreen(BottomNavigationScreen.ScreenB)
    }
}
NavHost(
    navController = navController,
    startDestination = BottomNavigationScreen.ScreenA.route
) {

findActivity:

fun Context.findActivity(): Activity? = when (this) {
    is Activity -> this
    is ContextWrapper -> baseContext.findActivity()
    else -> null
}

【讨论】:

  • 虽然这似乎确实解决了深层链接后的问题(即使没有按照您的建议进行手动深层链接处理),但它看起来像是在后台堆栈中添加了更多元素。因此,与其拥有最多 1 个屏幕 B 实例,我最终可以在不同的地方拥有很多。
  • @BarryIrvine 在最后一刻重构时犯了一个错误,没有检查它=)查看navigateBottomNavigationScreen的更新变体
  • 这样更好,因为所有屏幕都是有效的顶级屏幕,如果您按下返回,所有屏幕都会退出应用程序。我的第一个选项卡有问题,但无论如何这是基于我的实现。这是因为对于 ScreenA,我的起始目的地 = ComposableZ。但是 ComposableZ 有一个启动效果,可以在某些条件下启动 ComposableY。无论如何,您的新解决方案在深层链接之后会中断,尽管您的手动解决方案可能会修复它。也许我只是希望修复是在撰写导航的下一个版本中。希望明天有。
  • @BarryIrvine 我不确定这是否可以被视为错误,因此可能无法修复。从深度链接导航相当于调用navigate,我怀疑是否可以指定popUpTo。我认为手动指定startDestination 应该可以解决这个问题。您能否更清楚地描述您对我的解决方案的问题?如果您需要从ComposableZ 出发,您可以使用相同的navigateBottomNavigationScreen 方法(如果它必须转到底部导航路线之一)。
  • 当然,一旦处理了深层链接,那么返回起始目的地不应继续尝试再次处理深层链接(我认为这是正在发生的事情)
【解决方案2】:

看起来它终于在 2.4.0-beta02 版本中得到修复;所以这毕竟是一个错误。

我能够将 saveState 和 restoreState 命令添加回我的 BottomBar(根据文档),并且通过深层链接,我现在仍然能够单击初始目标。

【讨论】:

    猜你喜欢
    • 2021-12-15
    • 1970-01-01
    • 2022-06-27
    • 1970-01-01
    • 2020-07-13
    • 1970-01-01
    • 1970-01-01
    • 2023-03-03
    • 1970-01-01
    相关资源
    最近更新 更多