ネストされたグラフ

通常、アプリ内のログインフロー、ウィザード、その他のサブフローは、ネストされたナビゲーション グラフとして表現するのが最も効果的です。自己完結型のサブナビゲーション フローをこのようにネストすると、アプリの UI のメインフローの理解と管理が容易になります。

また、ネストグラフを再利用できます。また、一定のレベルのカプセル化も実現します。ネストグラフの外部にあるデスティネーションは、ネストグラフ内のデスティネーションに直接アクセスできません。代わりに、ネストグラフ自体に対して navigate() する必要があります。内部ロジックは、グラフの他の部分に影響を与えることなく変更できます。

アプリのトップレベル ナビゲーション グラフは、ユーザーがアプリを起動したときに最初に表示される最初のデスティネーションから始まり、ユーザーがアプリ内を移動する際に表示されるデスティネーションを含む必要があります。

図 1.トップレベル ナビゲーション グラフ

図 1 のトップレベル ナビゲーション グラフの例で、ユーザーがアプリを初めて起動したときに限り、title_screen 画面と register 画面を表示したいとします。その後、ユーザー情報が保存されます。その後のアプリの起動時には、直接 match 画面を表示する必要があります。

ベスト プラクティスとして、図 1 に示すように、match 画面をトップレベル ナビゲーション グラフの開始デスティネーションとして設定し、タイトル画面と登録画面をネストグラフに移動します。

図 2. トップレベル ナビゲーション グラフにネストグラフが追加されました。

match 画面が起動したら、登録済みユーザーがいるかどうかを確認します。ユーザーが登録されていない場合は、登録画面に移動します。

条件付きナビゲーションのシナリオの詳細については、条件付きナビゲーションをご覧ください。

Compose

Compose を使用してネストされたナビゲーション グラフを作成するには、NavGraphBuilder.navigation() 関数を使用します。グラフにデスティネーションを追加するときは、NavGraphBuilder.composable() 関数や NavGraphBuilder.dialog() 関数と同様に navigation() を使用します。

主な違いは、navigation では新しいデスティネーションではなく、ネストグラフが作成される点です。次に、navigation のラムダ内で composabledialog を呼び出して、ネストされたグラフにデスティネーションを追加します。

次のスニペットが Compose を使用して図 2 のグラフをどのように実装しているかを考えます。

NavHost(navController, startDestination = "title_screen") {
    composable("title_screen") {
        TitleScreen(
            onPlayClicked = { navController.navigate("register") },
            onLeaderboardsClicked = { /* Navigate to leaderboards */ }
        )
    }
    composable("register") {
        RegisterScreen(
            onSignUpComplete = { navController.navigate("gameInProgress") }
        )
    }
    navigation(startDestination = "match", route = "gameInProgress") {
        composable("match") {
            MatchScreen(
                onStartGame = { navController.navigate("in_game") }
            )
        }
        composable("in_game") {
            InGameScreen(
                onGameWin = { navController.navigate("results_winner") },
                onGameLose = { navController.navigate("game_over") }
            )
        }
        composable("results_winner") {
            ResultsWinnerScreen(
                onNextMatchClicked = {
                    navController.navigate("match") {
                        popUpTo("match") { inclusive = true }
                    }
                },
                onLeaderboardsClicked = { /* Navigate to leaderboards */ }
            )
        }
        composable("game_over") {
            GameOverScreen(
                onTryAgainClicked = {
                    navController.navigate("match") {
                        popUpTo("match") { inclusive = true }
                    }
                }
            )
        }
    }
}

ネストされたデスティネーションに直接移動するには、他のデスティネーションに移動する場合と同様に route を使用します。これは、ルートがどの画面でもナビゲートできるグローバル コンセプトであるためです。

navController.navigate("match")

拡張関数

NavGraphBuilder の拡張関数を使用して、グラフにデスティネーションを追加できます。これらの拡張関数は、ビルド済みの navigationcomposabledialog 拡張メソッドと併せて使用できます。

たとえば、拡張関数を使用して、前のセクションで説明したネストされたグラフを追加できます。

fun NavGraphBuilder.addNestedGraph(navController: NavController) {
    navigation(startDestination = "match", route = "gameInProgress") {
        composable("match") {
            MatchScreen(
                onStartGame = { navController.navigate("in_game") }
            )
        }
        composable("in_game") {
            InGameScreen(
                onGameWin = { navController.navigate("results_winner") },
                onGameLose = { navController.navigate("game_over") }
            )
        }
        composable("results_winner") {
            ResultsWinnerScreen(
                onNextMatchClicked = { navController.navigate("match") },
                onLeaderboardsClicked = { /* Navigate to leaderboards */ }
            )
        }
        composable("game_over") {
            GameOverScreen(
                onTryAgainClicked = { navController.navigate("match") }
            )
        }
    }
}

次に、ナビゲーションをインラインで呼び出す代わりに、NavHost に渡すラムダでこの関数を呼び出すことができます。例は次のとおりです。

@Composable
fun MyApp() {
    val navController = rememberNavController()
    NavHost(navController, startDestination = "title_screen") {
        composable("title_screen") {
            TitleScreen(
                onPlayClicked = { navController.navigate("register") },
                onLeaderboardsClicked = { /* Navigate to leaderboards */ }
            )
        }
        composable("register") {
            RegisterScreen(
                onSignUpComplete = { navController.navigate("gameInProgress") }
            )
        }

        // Add the nested graph using the extension function
        addNestedGraph(navController)
    }
}

XML

XML を使用する場合は、Navigation Editor を使用してネストグラフを作成できます。手順は次のとおりです。

  1. Navigation Editor で、Shift キーを長押ししながら、ネストグラフに含めるデスティネーションをクリックします。
  2. 右クリックしてコンテキスト メニューを開き、[Move to Nested Graph] > [New Graph] を選択します。対象デスティネーションがネストされたグラフ内に囲い込まれます。Navigation Editor 内のネストされたグラフを図 2 に示します。

    図 2. Navigation Editor 内のネストされたグラフ
  3. ネストグラフをクリックします。次の属性が [Attributes] パネルに表示されます。

    • Type - 「ネストされたグラフ」が格納されます。
    • ID - システムによって割り当てられたネストされたグラフの ID が格納されます。この ID を使用して、コードからネストされたグラフを参照できます。
  4. ネストされたグラフをダブルクリックすると、デスティネーションが表示されます。

  5. [Text] タブをクリックして、XML ビューに切り替えます。ネストされたナビゲーション グラフがグラフに追加されています。このナビゲーション グラフには、独自の navigation 要素と、独自の ID、ネストされたグラフの最初のデスティネーションを指す startDestination 属性があります。

    <?xml version="1.0" encoding="utf-8"?>
    <navigation xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:android="http://schemas.android.com/apk/res/android"
        app:startDestination="@id/mainFragment">
        <fragment
            android:id="@+id/mainFragment"
            android:name="com.example.cashdog.cashdog.MainFragment"
            android:label="fragment_main"
            tools:layout="@layout/fragment_main" >
            <action
                android:id="@+id/action_mainFragment_to_sendMoneyGraph"
                app:destination="@id/sendMoneyGraph" />
            <action
                android:id="@+id/action_mainFragment_to_viewBalanceFragment"
                app:destination="@id/viewBalanceFragment" />
        </fragment>
        <fragment
            android:id="@+id/viewBalanceFragment"
            android:name="com.example.cashdog.cashdog.ViewBalanceFragment"
            android:label="fragment_view_balance"
            tools:layout="@layout/fragment_view_balance" />
        <navigation android:id="@+id/sendMoneyGraph" app:startDestination="@id/chooseRecipient">
            <fragment
                android:id="@+id/chooseRecipient"
                android:name="com.example.cashdog.cashdog.ChooseRecipient"
                android:label="fragment_choose_recipient"
                tools:layout="@layout/fragment_choose_recipient">
                <action
                    android:id="@+id/action_chooseRecipient_to_chooseAmountFragment"
                    app:destination="@id/chooseAmountFragment" />
            </fragment>
            <fragment
                android:id="@+id/chooseAmountFragment"
                android:name="com.example.cashdog.cashdog.ChooseAmountFragment"
                android:label="fragment_choose_amount"
                tools:layout="@layout/fragment_choose_amount" />
        </navigation>
    </navigation>
    
  6. コードで、ルートグラフをネストグラフに接続するアクションのリソース ID を渡します。

    Kotlin

    view.findNavController().navigate(R.id.action_mainFragment_to_sendMoneyGraph)
    

    Java

    Navigation.findNavController(view).navigate(R.id.action_mainFragment_to_sendMoneyGraph);
    
  7. [Design] タブに戻り、[Root] をクリックして、ルートグラフに戻ります。

include を使用して他のナビゲーション グラフを参照する

グラフ構造をモジュール化するもう 1 つの方法は、親ナビゲーション グラフで <include> 要素を使用して、あるグラフを別のグラフに含めることです。これにより、インクルードされるグラフを別のモジュールまたはプロジェクトで完全に定義できるようになり、再利用性が最大化されます。

次のスニペットは <include> の使用方法を示しています。

<!-- (root) nav_graph.xml -->
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_graph"
    app:startDestination="@id/fragment">

    <strong><include app:graph="@navigation/included_graph" /></strong>

    <fragment
        android:id="@+id/fragment"
        android:name="com.example.myapplication.BlankFragment"
        android:label="Fragment in Root Graph"
        tools:layout="@layout/fragment_blank">
        <strong><action
            android:id="@+id/action_fragment_to_second_graph"
            app:destination="@id/second_graph" /></strong>
    </fragment>

    ...
</navigation>
<!-- included_graph.xml -->
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    <strong>android:id="@+id/second_graph"</strong>
    app:startDestination="@id/includedStart">

    <fragment
        android:id="@+id/includedStart"
        android:name="com.example.myapplication.IncludedStart"
        android:label="fragment_included_start"
        tools:layout="@layout/fragment_included_start" />
</navigation>

参考情報

ナビゲーションについて詳しくは、以下の参考情報をご確認ください。

サンプル

Codelab

動画