ネストされたグラフ

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

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

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

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

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

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

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

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

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

Compose

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

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

次のスニペットでは、Compose を使用して図 2 のグラフを実装しています。

// Routes
@Serializable object Title
@Serializable object Register

// Route for nested graph
@Serializable object Game

// Routes inside nested graph
@Serializable object Match
@Serializable object InGame
@Serializable object ResultsWinner
@Serializable object GameOver

NavHost(navController, startDestination = Title) {
   composable<Title> {
       TitleScreen(
           onPlayClicked = { navController.navigate(route = Register) },
           onLeaderboardsClicked = { /* Navigate to leaderboards */ }
       )
   }
   composable<Register> {
       RegisterScreen(
           onSignUpComplete = { navController.navigate(route = Game) }
       )
   }
   navigation<Game>(startDestination = Match) {
       composable<Match> {
           MatchScreen(
               onStartGame = { navController.navigate(route = InGame) }
           )
       }
       composable<InGame> {
           InGameScreen(
               onGameWin = { navController.navigate(route = ResultsWinner) },
               onGameLose = { navController.navigate(route = GameOver) }
           )
       }
       composable<ResultsWinner> {
           ResultsWinnerScreen(
               onNextMatchClicked = {
                   navController.navigate(route = Match) {
                       popUpTo(route = Match) { inclusive = true }
                   }
               },
               onLeaderboardsClicked = { /* Navigate to leaderboards */ }
           )
       }
       composable<GameOver> {
           GameOverScreen(
               onTryAgainClicked = {
                   navController.navigate(route = Match) {
                       popUpTo(route = Match) { inclusive = true }
                   }
               }
           )
       }
   }
}

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

navController.navigate(route = Match)

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

動画