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

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

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

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

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

マルチモジュール アプリの依存関係グラフサンプル
サンプルアプリの開始デスティネーション
図 1. サンプルアプリのアーキテクチャと開始デスティネーション

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

Groovy

dependencies {
    ...
    implementation project(":feature:home")
    implementation project(":feature:favorites")
    implementation project(":feature:settings")

Kotlin

dependencies {
    ...
    implementation(project(":feature:home"))
    implementation(project(":feature:favorites"))
    implementation(project(":feature: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/home_nav_graph">

    <include app:graph="@navigation/home_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/home_nav_graph">

    <include app:graph="@navigation/home_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/home_nav_graph"
        android:icon="@drawable/ic_home"
        android:title="Home"
        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 を使用して他のモジュールのデスティネーションに移動することはできません。代わりに、ディープリンクを使用して、暗黙的ディープリンクで関連付けられたデスティネーションに直接移動します。

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

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