使用功能模組進行導覽

動態導覽程式庫擴充了 Jetpack Navigation 元件的功能,以處理功能模組中定義的目的地。此程式庫還可讓您在瀏覽至這些目的地時,輕鬆安裝隨選的功能模組。

設定

如要支援功能模組,請在應用程式模組的 build.gradle 檔案中使用下列依附元件:

Groovy

dependencies {
    def nav_version = "2.7.7"

    api "androidx.navigation:navigation-fragment-ktx:$nav_version"
    api "androidx.navigation:navigation-ui-ktx:$nav_version"
    api "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"
}

Kotlin

dependencies {
    val nav_version = "2.7.7"

    api("androidx.navigation:navigation-fragment-ktx:$nav_version")
    api("androidx.navigation:navigation-ui-ktx:$nav_version")
    api("androidx.navigation:navigation-dynamic-features-fragment:$nav_version")
}

請注意,其他導覽依附元件應使用API 設定,以便可用於功能模組。

基本用法

若要支援功能模組,請先將應用程式中的所有 NavHostFragment 執行個體變更為 androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment

<androidx.fragment.app.FragmentContainerView
    android:id="@+id/nav_host_fragment"
    android:name="androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment"
    app:navGraph="@navigation/nav_graph"
    ... />

接著,在任一個 <activity><fragment> 中新增 app:moduleName 屬性,或是 位於com.android.dynamic-feature模組導航圖中與 DynamicNavHostFragment 有關的 <navigation> 目的地。此屬性會告訴動態導覽程式庫,該目的地屬於您指定的功能模組。

<fragment
    app:moduleName="myDynamicFeature"
    android:id="@+id/featureFragment"
    android:name="com.google.android.samples.feature.FeatureFragment"
    ... />

當您瀏覽至其中一個目的地時,動態導覽程式庫會先檢查是否已安裝該功能模組。如果功能模組已存在,應用程式會如預期地瀏覽至目的地。如果模組不存在,則應用程式在安裝模組時會立即顯示中繼的進度片段畫面。進度片段預設的執行方式,會顯示具有進度列的基本使用者介面,並處理任何發生的安裝錯誤。

首次瀏覽至功能模組時,會顯示兩個含有進度列的使用者介面畫面
圖 1 當使用者首次瀏覽至隨選功能時,使用者介面顯示了一個進度列。應用程式會將此畫面作為相應的模組下載內容顯示這個畫面。

若要自訂此使用者介面,或從自有的應用程式畫面手動處理安裝進度,請參閱自訂進度片段監控要求狀態一節中的這個主題。

未指定 app:moduleName 的目的地可以在沒有變更的情況下繼續運作,並且如同應用程式在一般 NavHostFragment 的情況下運作。

自訂進度片段

app:progressDestination 屬性設為您要用於處理安裝進度的目的地 ID,即可覆寫各個導覽圖在進度片段上的實作內容。自訂進度目的地應為來自於 AbstractProgressFragmentFragment。您必須覆寫有關安裝進度、錯誤和其他事件通知的摘要方法。接著才可以在您選擇的使用者介面中顯示安裝進度。

DefaultProgressFragment 類別的預設作法會使用到此 API 顯示安裝進度。

監控要求狀態

您可利用動態導覽程式庫實作出與用於隨選提供的使用者體驗最佳做法相似的使用者體驗流程,讓使用者在等待安裝作業完成時可繼續留在上一個畫面中。這表示您完全不需要顯示中繼的使用者介面或進度片段。

顯示底部導覽列的畫面,其中具有表示正在下載功能模組的圖示
圖 2 在底部導覽列顯示下載進度的畫面。

在這種情況下,您必須負責監控及處理所有安裝狀態、進度變更和錯誤等。

如要啟動此非封鎖性的導覽流程 ,請將含有 DynamicInstallMonitorDynamicExtras 物件傳遞給NavController.navigate(),如下列範例所示:

Kotlin

val navController = ...
val installMonitor = DynamicInstallMonitor()

navController.navigate(
    destinationId,
    null,
    null,
    DynamicExtras(installMonitor)
)

Java

NavController navController = ...
DynamicInstallMonitor installMonitor = new DynamicInstallMonitor();

navController.navigate(
    destinationId,
    null,
    null,
    new DynamicExtras(installMonitor);
)

呼叫 navigate() 後,應立即檢查 installMonitor.isInstallRequired 的值,確認嘗試瀏覽的結果是否為功能模組安裝。

  • 如果值為 false,您將會瀏覽至正常的目的地,無須採取其他行動。
  • 如果值是 true,則應開始觀察現在在 installMonitor.status 中的 LiveData 物件。此 LiveData 物件會從 Play Core 程式庫發送 SplitInstallSessionState 更新。這些更新包含安裝進度事件,可用來更新 UI。請記得處理 Play Core 指南中列出的所有相關狀態,包括必要時要求使用者確認

    Kotlin

    val navController = ...
    val installMonitor = DynamicInstallMonitor()
    
    navController.navigate(
      destinationId,
      null,
      null,
      DynamicExtras(installMonitor)
    )
    
    if (installMonitor.isInstallRequired) {
      installMonitor.status.observe(this, object : Observer<SplitInstallSessionState> {
          override fun onChanged(sessionState: SplitInstallSessionState) {
              when (sessionState.status()) {
                  SplitInstallSessionStatus.INSTALLED -> {
                      // Call navigate again here or after user taps again in the UI:
                      // navController.navigate(destinationId, destinationArgs, null, null)
                  }
                  SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION -> {
                      SplitInstallManager.startConfirmationDialogForResult(...)
                  }
    
                  // Handle all remaining states:
                  SplitInstallSessionStatus.FAILED -> {}
                  SplitInstallSessionStatus.CANCELED -> {}
              }
    
              if (sessionState.hasTerminalStatus()) {
                  installMonitor.status.removeObserver(this);
              }
          }
      });
    }
    

    Java

    NavController navController = ...
    DynamicInstallMonitor installMonitor = new DynamicInstallMonitor();
    
    navController.navigate(
      destinationId,
      null,
      null,
      new DynamicExtras(installMonitor);
    )
    
    if (installMonitor.isInstallRequired()) {
      installMonitor.getStatus().observe(this, new Observer<SplitInstallSessionState>() {
          @Override
          public void onChanged(SplitInstallSessionState sessionState) {
              switch (sessionState.status()) {
                  case SplitInstallSessionStatus.INSTALLED:
                      // Call navigate again here or after user taps again in the UI:
                      // navController.navigate(mDestinationId, mDestinationArgs, null, null);
                      break;
                  case SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION:
                      SplitInstallManager.startConfirmationDialogForResult(...)
                      break;
    
                  // Handle all remaining states:
                  case SplitInstallSessionStatus.FAILED:
                      break;
                  case SplitInstallSessionStatus.CANCELED:
                      break;
              }
    
              if (sessionState.hasTerminalStatus()) {
                  installMonitor.getStatus().removeObserver(this);
              }
          }
      });
    }
    

安裝完成後,LiveData 物件會發出 SplitInstallSessionStatus.INSTALLED 狀態。接著應再次呼叫 NavController.navigate()。由於模組已安裝完畢,呼叫現在會成功,應用程式會如預期地瀏覽前往目的地。

達到終端機狀態後(例如安裝完成或安裝失敗),應移除 LiveData 觀察器以避免記憶體流失。您可以使用 SplitInstallSessionStatus.hasTerminalStatus() 來確認狀態是否為終端狀態。

如需這項觀察器的實作範例,請參閱 AbstractProgressFragment

包含的圖表

動態導覽程式庫可支援納入在功能模組中定義的圖表。若要納入功能模組中定義的圖表,請採取下列步驟:

  1. 請使用 <include-dynamic/> 而非 <include/>,如以下範例所示:

    <include-dynamic
        android:id="@+id/includedGraph"
        app:moduleName="includedgraphfeature"
        app:graphResName="included_feature_nav"
        app:graphPackage="com.google.android.samples.dynamic_navigator.included_graph_feature" />
    
  2. <include-dynamic ... /> 中,必須指定下列屬性:

    • app:graphResName:導覽圖的資源檔案名稱。名稱衍生自圖表的檔案名稱。舉例來說,如果圖表位於 res/navigation/nav_graph.xml,則資源名稱為 nav_graph
    • android:id:圖表的目的地 ID。動態導覽程式庫會忽略所納入圖表中根元素的任何 android:id 值。
    • app:moduleName:模組的套件名稱。

使用正確的圖形套件

請務必取得正確 app:graphPackage,否則,導覽元件將無法從功能模組中納入指定的 navGraph

動態功能模組套件的名稱,會是將基本應用程式模組的 applicationId 再加上模組名稱構成。所以,如果基本應用程式模組有 com.example.dynamicfeatureappapplicationId,而動態功能模組的名稱為 DynamicFeatureModule,則動態模組的套件名稱將會是 com.example.dynamicfeatureapp.DynamicFeatureModule。此套件名稱區分大小寫。

如有任何問題,可以檢查產生的 AndroidManifest.xml 以確認功能模組的套件名稱。專案建構完成後,請前往 <DynamicFeatureModule>/build/intermediates/merged_manifest/debug/AndroidManifest.xml;如下所示:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:dist="http://schemas.android.com/apk/distribution"
    featureSplit="DynamicFeatureModule"
    package="com.example.dynamicfeatureapp"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="21"
        android:targetSdkVersion="30" />

    <dist:module
        dist:instant="false"
        dist:title="@string/title_dynamicfeaturemodule" >
        <dist:delivery>
            <dist:install-time />
        </dist:delivery>

        <dist:fusing dist:include="true" />
    </dist:module>

    <application />

</manifest>

featureSplit 值應與動態功能模組的名稱相符,且套件必須與基本應用程式模組的 applicationId 相符。app:graphPackage 是以下的組合:com.example.dynamicfeatureapp.DynamicFeatureModule

只能前往 include-dynamic 導覽圖中的 startDestination。動態模組負責其自身的導覽圖,而基本應用程式對其不會有任何認知。

內含的動態機制可讓基本應用程式模組納入在動態模組中定義的巢狀結構導覽圖。此巢狀結構導覽圖的運作方式與任何巢狀結構導覽圖相同。根導覽圖 (即巢狀結構圖的父項) 只能將巢狀結構導覽圖本身定義為目的地,無法定義其子項。因此,當內含的動態導覽圖是目的地時,就可以使用 startDestination

限制

  • 內含的動態圖目前不支援深層連結。
  • 動態載入的巢狀結構圖(即包含 app:moduleName<navigation> 元素)目前不支援深層連結。