Menavigasi dengan modul fitur

Library Navigator Dinamis memperluas fungsi komponen Navigasi Jetpack untuk berfungsi dengan tujuan yang ditentukan dalam modul fitur. Library ini juga menyediakan penginstalan modul fitur on-demand tanpa hambatan saat menavigasi ke tujuan ini.

Penyiapan

Untuk mendukung modul fitur, gunakan dependensi berikut dalam file build.gradle modul aplikasi Anda:

Groovy

dependencies {
    def nav_version = "2.8.0"

    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.8.0"

    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")
}

Perhatikan bahwa dependensi Navigasi lainnya harus menggunakan konfigurasi api agar tersedia untuk modul fitur Anda.

Penggunaan dasar

Untuk mendukung modul fitur, ubah semua instance NavHostFragment di aplikasi ke androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment terlebih dahulu:

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

Selanjutnya, tambahkan atribut app:moduleName ke tujuan <activity>, <fragment>, atau <navigation> dalam grafik navigasi modul com.android.dynamic-feature yang terkait dengan DynamicNavHostFragment. Atribut ini memberi tahu library Navigator Dinamis bahwa tujuan dimiliki oleh modul fitur dengan nama yang Anda tentukan.

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

Saat Anda menavigasi ke salah satu tujuan tersebut, library Navigator Dinamis akan memeriksa terlebih dahulu apakah modul fitur telah diinstal. Jika modul fitur sudah ada, aplikasi Anda akan menavigasi ke tujuan seperti yang diharapkan. Jika modul tidak ada, aplikasi Anda akan menampilkan tujuan fragmen progres menengah saat menginstal modul. Implementasi default fragmen progres menampilkan UI dasar dengan status progres dan menangani error penginstalan.

dua layar pemuatan yang menampilkan UI dengan status progres saat menavigasi ke
         modul fitur untuk pertama kalinya
Gambar 1. UI yang menampilkan status progres saat pengguna membuka fitur on-demand untuk pertama kalinya. Aplikasi menampilkan layar ini sebagai hasil download modul yang sesuai.

Untuk menyesuaikan UI ini, atau untuk menangani progres penginstalan secara manual dari dalam layar aplikasi Anda, lihat bagian Menyesuaikan fragmen progres dan Memantau status permintaan dalam topik ini.

Tujuan yang tidak menentukan app:moduleName tetap berfungsi tanpa perubahan dan berperilaku seolah-olah aplikasi Anda menggunakan NavHostFragment reguler.

Menyesuaikan fragmen progres

Anda dapat mengganti implementasi fragmen progres untuk setiap grafik navigasi dengan menyetel atribut app:progressDestination ke ID tujuan yang ingin Anda gunakan untuk menangani progres penginstalan. Tujuan progres khusus Anda harus berupa Fragment yang berasal dari AbstractProgressFragment. Anda harus mengganti metode abstrak untuk notifikasi tentang progres penginstalan, error, dan peristiwa lainnya. Anda dapat menampilkan progres penginstalan di UI pilihan Anda.

Class DefaultProgressFragment penerapan default menggunakan API ini untuk menampilkan progres penginstalan.

Memantau status permintaan

Library Navigator Dinamis memungkinkan Anda menerapkan alur UX yang serupa dengan yang ada di Praktik terbaik UX untuk pengiriman on demand, di mana pengguna tetap dalam konteks layar sebelumnya selagi menunggu penginstalan selesai. Dengan cara ini, Anda tidak perlu menampilkan UI menengah atau fragmen progres sama sekali.

layar yang menampilkan menu navigasi bawah dengan ikon yang menunjukkan
         bahwa modul fitur sedang didownload
Gambar 2. Layar yang menampilkan progres download dari menu navigasi bawah.

Dalam skenario ini, Anda bertanggung jawab untuk memantau dan menangani semua status penginstalan, perubahan progres, error, dan sebagainya.

Untuk memulai alur navigasi non-blocking ini, teruskan objek DynamicExtras yang berisi DynamicInstallMonitor ke NavController.navigate(), seperti yang ditunjukkan dalam contoh berikut:

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);
)

Setelah memanggil navigate(), Anda harus memeriksa nilai installMonitor.isInstallRequired untuk melihat apakah navigasi yang dicoba menghasilkan penginstalan modul fitur.

  • Jika nilainya false, Anda akan menavigasi ke tujuan normal dan tidak perlu melakukan apa pun.
  • Jika nilainya adalah true, Anda harus mulai mengamati objek LiveData yang sekarang berada di installMonitor.status. Objek LiveData ini memberikan update SplitInstallSessionState dari library Play Core. Update ini berisi peristiwa progres penginstalan yang dapat Anda gunakan untuk mengupdate UI. Ingatlah untuk menangani semua status terkait seperti yang diuraikan dalam panduan Play Core, termasuk meminta konfirmasi pengguna jika diperlukan.

    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);
              }
          }
      });
    }
    

Setelah penginstalan selesai, objek LiveData akan menghasilkan status SplitInstallSessionStatus.INSTALLED. Kemudian Anda harus memanggil NavController.navigate() lagi. Karena modul telah terinstal, panggilan sekarang akan berhasil berjalan dan aplikasi akan menavigasi ke tujuan seperti yang diharapkan.

Setelah mencapai status terminal, seperti saat penginstalan selesai atau saat penginstalan gagal, Anda harus menghapus LiveData observer untuk menghindari kebocoran memori. Anda dapat memeriksa apakah status mewakili status terminal dengan menggunakan SplitInstallSessionStatus.hasTerminalStatus().

Lihat AbstractProgressFragment untuk contoh penerapan pengamat ini.

Grafik yang disertakan

Library Navigator Dinamis mendukung penyertaan grafik yang ditentukan dalam modul fitur. Untuk menyertakan grafik yang ditentukan dalam modul fitur, lakukan hal berikut:

  1. Gunakan <include-dynamic/>, bukan <include/>, seperti yang ditunjukkan pada contoh berikut:

    <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. Di dalam <include-dynamic ... />, Anda harus menentukan atribut berikut:

    • app:graphResName: nama file resource grafik navigasi. Nama diambil dari nama file grafik. Misalnya, jika grafik menggunakan res/navigation/nav_graph.xml, nama resource-nya adalah nav_graph.
    • android:id - ID tujuan grafik. Library Navigator Dinamis mengabaikan nilai android:id mana pun yang ditemukan dalam elemen root grafik yang disertakan.
    • app:moduleName: nama paket modul.

Menggunakan graphPackage yang benar

Penting untuk memastikan bahwa app:graphPackage benar karena komponen Navigasi tidak akan dapat menyertakan navGraph yang ditentukan dari modul fitur.

Nama paket modul fitur dinamis dikonstruksi dengan menambahkan nama modul ke applicationId modul aplikasi dasar. Jadi, jika modul aplikasi dasar memiliki applicationId dari com.example.dynamicfeatureapp dan modul fitur dinamis bernama DynamicFeatureModule, maka nama paket modul dinamis akan menjadi com.example.dynamicfeatureapp.DynamicFeatureModule. Nama paket ini peka terhadap huruf besar/kecil.

Jika ragu, Anda dapat memastikan nama paket modul fitur dengan memeriksa AndroidManifest.xml yang dihasilkan. Setelah mem-build project, buka <DynamicFeatureModule>/build/intermediates/merged_manifest/debug/AndroidManifest.xml, yang akan terlihat seperti berikut:

<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>

Nilai featureSplit harus cocok dengan nama modul fitur dinamis, dan paket akan cocok dengan applicationId modul aplikasi dasar. app:graphPackage adalah kombinasi dari ini: com.example.dynamicfeatureapp.DynamicFeatureModule.

Anda hanya dapat menavigasi ke startDestination grafik navigasi include-dynamic. Modul dinamis bertanggung jawab atas grafik navigasinya sendiri dan aplikasi dasarnya tidak memiliki pengetahuan tentang hal tersebut.

Mekanisme include-dynamic memungkinkan modul aplikasi dasar menyertakan grafik navigasi bertingkat yang ditentukan dalam modul dinamis. Grafik navigasi bertingkat ini berperilaku seperti grafik navigasi bertingkat lainnya. Grafik navigasi root (yaitu, induk grafik bertingkat) hanya dapat menentukan grafik navigasi bertingkat itu sendiri sebagai tujuan dan bukan turunannya. Dengan demikian, startDestination digunakan saat grafik navigasi include-dynamicnavigation menjadi tujuan.

Batasan

  • Grafik yang disertakan secara dinamis saat ini tidak mendukung deep link.
  • Grafik bertingkat yang dimuat secara dinamis (yaitu, elemen <navigation> dengan app:moduleName) saat ini tidak mendukung deep link.