多模組專案的導覽最佳做法

導覽圖可包含以下項目的任意組合:

  • 單一目的地,例如 <fragment> 目的地。
  • 一個巢狀圖,當中包含一組相關目的地。
  • 一個 <include> 元素,可讓您以類似巢狀結構的方式嵌入另一個導覽圖檔案。

這種彈性設計可讓您結合較小的導覽圖,構成應用程式的完整導覽圖,即使這些較小的導覽圖是由不同的模組提供也沒問題。

針對這個主題中的範例,每個功能模組都著重於一個功能,並提供單一導覽圖,當中包含實作該功能所需的所有目的地。正式版應用程式可能會在較低層級提供多個子模組,這些子模組就是這個較高層級功能模組的實作詳細資料。這些功能模組全都直接或間接包含在 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 模組負責為應用程式提供完整圖表,並將 NavHost 新增至 UI。在 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. 中定義的目的地。

應用程式模組中的頂層導覽

導覽元件含有 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 進行瀏覽不同,您可以瀏覽至所有圖表中的任何 URI,甚至可以跨模組進行瀏覽。

使用 URI 進行瀏覽時,系統「不會」重設返回堆疊。這項行為與明確深層連結導覽不同。在明確深層連結導覽流程中,返回堆疊會在導覽時遭取代。