◀️

[Jetpack Compose] NavigationBar のタブ選択中にタブをタップしたときにタブの開始画面に遷移する

2023/08/09に公開

はじめに

前回の記事 の発展で、タイトルの動きを実装します。

コード

class BarItem(
    val label: String,
    val icon: ImageVector,
    val route: String,
)

@Composable
fun MyAppNavigationBar(
    navController: NavController,
) {
    val backStackEntry by navController.currentBackStackEntryAsState()
    val currentDestination = backStackEntry?.destination

    val barItems = listOf(
        BarItem("Home", Icons.Outlined.Home, Route.Home.route),
        BarItem("Books", Icons.Outlined.List, Route.Books.route),
        BarItem("Settings", Icons.Outlined.Settings, Route.Settings.route)
    )

    NavigationBar {
        barItems.forEach { item ->
            val selected = currentDestination?.hierarchy?.any { it.route == item.route } == true

            NavigationBarItem(
                selected = selected,
                onClick = { onBarItemClick(item, currentDestination, navController) },
                label = { Text(text = item.label) },
                icon = { Icon(item.icon, contentDescription = null) }
            )
        }
    }
}
    
/**
 * NavigationBarItem.onClick に与えるコールバック
 */
fun onBarItemClick(item: BarItem, currentDestination: NavDestination?, navController: NavController) {
    val tabStartDestination = currentDestination?.parent?.startDestinationRoute

    if (
        currentDestination != null
        && currentDestination.hierarchy.any { it.route == item.route }
        && currentDestination.parent != navController.graph
        && tabStartDestination != null
        && currentDestination.route != tabStartDestination
    ) {
        // タブの開始画面まで戻る
        Log.d("App", "popup")
        val result = navController.popBackStack(
            route = tabStartDestination,
            inclusive = false,
            saveState = false,
        )
	// 戻ることに失敗した場合(直接別タブから詳細ページに飛んだため、
	// タブの開始画面がバックスタックにない場合など)はタブの開始画面に遷移する
        if (!result) {
            navController.navigate(tabStartDestination) {
                popUpTo(navController.graph.findStartDestination().id) {
                    saveState = true
                }
                launchSingleTop = true
                restoreState = true
            }
        }

    } else {
        Log.d("App", "switch tab")

        navController.navigate(item.route) {
            popUpTo(navController.graph.findStartDestination().id) {
                saveState = true
            }
            launchSingleTop = true
            restoreState = true
        }
    }
}

解説

NavDestination.parent で 親の NavGraph? 、 NavGraph.startDestinationRoute でそのグラフの開始画面に設定した NavDestination.route が取得できるので、

// タブ選択状態で
currentDestination.hierarchy.any { it.route == item.route }

// 現在の画面がネストされたグラフ(親がルートではない)であり
currentDestination.parent != navController.graph

// 現在の画面がタブの開始画面ではない場合
currentDestination.route != tabStartDestination

// タブの開始画面まで戻る

という感じで書けます。

終わりに

もっとシンプルに書く方法があれば教えてください!!!

Discussion