マルチモジュール プロジェクトのナビゲーションに関するベスト プラクティス

ナビゲーション グラフは、以下のものを任意に組み合わせて構成できます。

  • 単一のデスティネーション(<fragment> デスティネーションなど)。
  • 関連するデスティネーションのセットをカプセル化したネストグラフ
  • <include> 要素。これを使用すると、別のナビゲーション グラフ ファイルを、ネストグラフと同じように埋め込むことができます。

こうした柔軟性により、小さなナビゲーション グラフを結合して、アプリの完全なナビゲーション グラフを作成できます。これは、小さなナビゲーション グラフが別のライブラリ モジュールによって提供される場合も同じです。

このトピックの例では、各ライブラリ モジュールは 1 つの機能に焦点を絞っており、その機能を実装するのに必要なすべてのデスティネーションをカプセル化した単一のナビゲーション グラフを提供します。製品版アプリでは、下位レベルにある多くのサブモジュールに、上位レベルのライブラリ モジュールの実装の詳細を含める場合もあります。このようなライブラリ モジュールは、それぞれ直接的または間接的に app モジュールに組み込まれています。このドキュメントでサンプルとして使用するマルチモジュール アプリの構造を次に示します。

サンプルアプリのアプリ アーキテクチャ
サンプルアプリの開始デスティネーション
図 1. サンプルアプリのアプリ アーキテクチャと開始デスティネーション

各ライブラリ モジュールは、独自のナビゲーション グラフとデスティネーションを持つ自己完結型のユニットです。次に示すように、app モジュールはそれぞれに依存しており、それぞれを実装の詳細として build.gradle ファイルに追加します。

dependencies {
    ...
    implementation project(":list")
    implementation project(":favorites")
    implementation project(":settings")

app モジュールの役割

app モジュールは、アプリの完全なグラフを提供し、UI に NavHost を追加します。app モジュールのナビゲーション グラフ内では、<include> を使用してライブラリ グラフを参照できます。<include> を使用するのはネストグラフを使用するのと機能的には同じですが、<include> は他のプロジェクト モジュールまたはライブラリ プロジェクトのグラフをサポートします。次の例をご覧ください。

<?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/list_nav_graph">

    <include app:graph="@navigation/list_navigation" />
    <include app:graph="@navigation/favorites_navigation" />
    <include app:graph="@navigation/settings_navigation" />
</navigation>

トップレベル ナビゲーション グラフにライブラリが含まれている場合は、必要に応じてライブラリ グラフにナビゲートできます。たとえば、次に示すように、ナビゲーション グラフ内のフラグメントから設定グラフに移動するアクションを作成できます。

<?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/list_nav_graph">

    <include app:graph="@navigation/list_navigation" />
    <include app:graph="@navigation/favorites_navigation" />
    <include app:graph="@navigation/settings_navigation" />

    <fragment
        android:id="@+id/random_fragment"
        android:name="com.example.android.RandomFragment"
        android:label="@string/fragment_random" >
        <!-- Launch into Settings Navigation Graph -->
        <action
            android:id="@+id/action_random_fragment_to_settings_nav_graph"
            app:destination="@id/settings_nav_graph" />
    </fragment>
</navigation>

複数のライブラリ モジュールが共通のデスティネーション セットを参照する必要がある場合(ログイングラフなど)、そうした共通デスティネーションを各ライブラリ モジュールのナビゲーション グラフに含めるべきではありません。代わりに、共通デスティネーションを app モジュールのナビゲーション グラフに追加します。そうすれば、各ライブラリ モジュールはライブラリ モジュール間をナビゲートして、共通デスティネーションに移動できます。

上記の例では、アクションで @id/settings_nav_graph というナビゲーション デスティネーションが指定されています。この ID は、インクルードされたグラフ @navigation/settings_navigation. 内で定義されているデスティネーションを指しています。

アプリ モジュール内のトップレベル ナビゲーション

Navigation コンポーネントには、NavigationUI クラスがあります。このクラスには、トップ アプリバー、ナビゲーション ドロワー、ボトム ナビゲーションを使用してナビゲーションを管理する静的メソッドが含まれています。アプリのトップレベル デスティネーションがライブラリ モジュールによって提供される UI 要素で構成されている場合、トップレベル ナビゲーションおよび UI 要素を配置する場所としては app モジュールが適切です。アプリ モジュールは共同で動作するライブラリ モジュールに依存しているので、すべてのデスティネーションは、アプリ モジュール内で定義されているコードからアクセス可能です。つまり、メニュー項目の ID がデスティネーションの ID と一致する場合、NavigationUI を使用して、デスティネーションをメニュー項目に関連付けることができます。

図 2 では、サンプル app モジュールにより、メイン アクティビティに BottomNavigationView が定義されています。メニュー内のメニュー項目 ID は、ライブラリ グラフのナビゲーション グラフ ID と一致します。

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@id/list_nav_graph"
        android:icon="@drawable/ic_list"
        android:title="List"
        app:showAsAction="ifRoom"/>

    <item
        android:id="@id/favorites_nav_graph"
        android:icon="@drawable/ic_favorite"
        android:title="Favorites"
        app:showAsAction="ifRoom"/>

    <item
        android:id="@id/settings_nav_graph"
        android:icon="@drawable/ic_settings"
        android:title="Settings"
        app:showAsAction="ifRoom" />
</menu>

NavigationUIボトム ナビゲーションを処理するには、次に示すように、メイン アクティビティ クラスの onCreate() から setupWithNavController() を呼び出します。

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController

    findViewById<BottomNavigationView>(R.id.bottom_nav)
            .setupWithNavController(navController)
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    NavHostFragment navHostFragment =
            (NavHostFragment) supportFragmentManager.findFragmentById(R.id.nav_host_fragment);
    NavController navController = navHostFragment.getNavController();
    BottomNavigationView bottomNav = findViewById(R.id.bottom_nav);

    NavigationUI.setupWithNavController(bottomNav, navController);
}

このコードを配置すると、ユーザーがボトム ナビゲーション項目をクリックしたとき、NavigationUI は適切なライブラリ グラフに移動します。

一般的に、ライブラリ モジュールのナビゲーション グラフ内の深いレベルに埋め込まれている特定のデスティネーションへの依存関係をアプリ モジュールにハードコードすることは避けるべきです。ほとんどの場合は、埋め込まれたかインクルードされたナビゲーション グラフへのエントリ ポイントのみをアプリ モジュールに認識させるのが適切です(これは、ライブラリ モジュール以外の場合も同様です)。ライブラリのナビゲーション グラフ内の深いレベルにあるデスティネーションにリンクする必要がある場合は、ディープリンクを使用する方法をおすすめします。ディープリンクは、ライブラリが別のライブラリのナビゲーション グラフ内のデスティネーションにナビゲートするための唯一の方法でもあります。

ライブラリ モジュール間のナビゲート

コンパイル時には、独立したライブラリ モジュールはお互いを確認できないので、ID を使用して他のモジュールのデスティネーションに移動することはできません。代わりに、ディープリンクを使用して、暗黙的ディープリンクで関連付けられたデスティネーションに直接移動します。

前述の例で、リスト モジュールのボタンから、設定モジュールでネストされているデスティネーションに移動する必要がある場合を考えてみましょう。そのためには、次に示すように、設定ナビゲーション グラフ内のデスティネーションへのディープリンクを追加します。

<?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/settings_nav_graph"
    app:startDestination="@id/settings_fragment_one">

    ...

    <fragment
        android:id="@+id/settings_fragment_two"
        android:name="com.example.google.login.SettingsFragmentTwo"
        android:label="@string/settings_fragment_two" >

        <deepLink
            app:uri="android-app://example.google.app/settings_fragment_two" />
    </fragment>
</navigation>

次に、リスト フラグメント内のボタンの onClickListener に以下のコードを追加します。

Kotlin

button.setOnClickListener {
    val request = NavDeepLinkRequest.Builder
        .fromUri("android-app://example.google.app/settings_fragment_two".toUri())
        .build()
    findNavController().navigate(request)
}

Java

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        NavDeepLinkRequest request = NavDeepLinkRequest.Builder
            .fromUri(Uri.parse("android-app://example.google.app/settings_fragment_two"))
            .build();
        NavHostFragment.findNavController(this).navigate(request);
    }
});

アクション ID またはデスティネーション ID を使用したナビゲーションとは異なり、モジュール間であっても、任意のグラフ内の任意の URI に移動できます。

URI を使用して移動する場合、バックスタックはリセットされません。この動作は、ナビゲーション時にバックスタックが置き換えられる明示的ディープリンクのナビゲーションとは異なります。