Dùng mô-đun tính năng để điều hướng

Thư viện Dynamic Navigator sẽ mở rộng chức năng của thành phần Điều hướng Jetpack để liên kết với các đích đến được xác định trong mô-đun tính năng. Thư viện này cũng cung cấp dịch vụ cài đặt liền mạch các mô-đun tính năng theo yêu cầu khi điều hướng đến những đích đến này.

Thiết lập

Để hỗ trợ các mô-đun tính năng, hãy sử dụng các phần phụ thuộc sau trong tệp build.gradle của mô-đun ứng dụng:

Groovy

dependencies {
    def nav_version = "2.8.4"

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

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

Lưu ý các phần phụ thuộc Điều hướng khác phải dùng cấu hình api để luôn sử dụng được cho các mô-đun tính năng.

Cách dùng cơ bản

Để hỗ trợ các mô-đun tính năng, trước tiên hãy thay đổi mọi NavHostFragment trong ứng dụng thành 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"
    ... />

Tiếp theo, hãy thêm thuộc tính app:moduleName vào bất kỳ đích đến <activity>, <fragment> hoặc <navigation> nào trong biểu đồ điều hướng của mô-đun com.android.dynamic-feature được liên kết vớiDynamicNavHostFragment. Thuộc tính này báo cho Thư viện Dynamic Navigator biết đích đến thuộc một mô-đun tính năng với tên đã được chỉ định.

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

Khi bạn điều hướng đến một trong các đích đến này, trước tiên Thư viện Dynamic Navigator sẽ kiểm tra xem mô-đun tính năng có được cài đặt chưa. Nếu đã có mô-đun tính năng, ứng dụng sẽ điều hướng đến đích đến như dự kiến. Nếu không có mô-đun, ứng dụng sẽ hiển thị đích đến là một mảnh tiến trình trung gian khi cài đặt mô-đun. Theo mặc định, khi triển khai mảnh tiến trình, màn hình sẽ hiển thị giao diện người dùng cơ bản với thanh tiến trình và xử lý mọi lỗi cài đặt.

hai màn hình tải dữ liệu cho thấy giao diện người dùng với thanh tiến trình khi điều hướng đến một mô-đun tính năng lần đầu
Hình 1. Giao diện người dùng cho thấy thanh tiến trình khi người dùng chuyển đến một tính năng theo yêu cầu lần đầu. Ứng dụng sẽ hiển thị màn hình này dưới dạng mô-đun tải xuống tương ứng.

Để tùy chỉnh giao diện người dùng hoặc xử lý theo cách thủ công quá trình cài đặt từ trong màn hình ứng dụng, hãy xem phần Tùy chỉnh mảnh tiến trìnhTheo dõi trạng thái yêu cầu trong chủ đề này.

Các đích đến không chỉ định app:moduleName vẫn tiếp tục hoạt động mà không có các thay đổi đồng thời chúng vận hành như thể ứng dụng sử dụng NavHostFragment thông thường.

Tùy chỉnh mảnh tiến trình

Có thể ghi đè phương thức triển khai mảnh tiến trình cho từng biểu đồ điều hướng bằng cách đặt thuộc tính app:progressDestination thành ID của đích đến mà bạn muốn sử dụng để xử lý tiến trình cài đặt. Đích đến tiến trình tuỳ chỉnh của bạn phải là Fragment bắt nguồn từ AbstractProgressFragment. Bạn phải ghi đè các phương thức trừu tượng (abstract) cho thông báo về tiến trình cài đặt, lỗi và các sự kiện khác. Sau đó, bạn có thể hiển thị tiến trình cài đặt trong giao diện người dùng đã chọn.

Lớp DefaultProgressFragment của quy trình triển khai mặc định sẽ sử dụng API này để hiển thị tiến trình cài đặt.

Theo dõi trạng thái của yêu cầu

Thư viện Dynamic Navigator cho phép triển khai một luồng UX (trải nghiệm người dùng) tương tự như trong Các phương pháp thiết kế trải nghiệm người dùng hay nhất để phân phối theo yêu cầu, trong đó người dùng đợi ở màn hình trước trong khi chờ quá trình cài đặt hoàn tất. Nghĩa là bạn không cần hiển thị giao diện người dùng trung gian hay mảnh tiến trình.

màn hình hiển thị thanh điều hướng ở dưới cùng với một biểu tượng cho biết mô-đun tính năng đang được tải xuống
Hình 2. Màn hình hiển thị tiến trình tải xuống trong thanh điều hướng bên dưới.

Trong trường hợp này, bạn chịu trách nhiệm theo dõi và xử lý tất cả các trạng thái cài đặt cũng như thay đổi về tiến trình, lỗi, v.v.

Để bắt đầu luồng điều hướng không bị chặn này, hãy truyền đối tượng DynamicExtras chứa DynamicInstallMonitor đến NavController.navigate(), như trong ví dụ sau:

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

Ngay sau khi gọi navigate(), bạn phải kiểm tra giá trị của installMonitor.isInstallRequired để xem liệu thao tác điều hướng đã thử có cài đặt mô-đun tính năng hay không.

  • Nếu giá trị là false, tức là bạn đang điều hướng đến một đích đến bình thường và không cần làm gì thêm.
  • Nếu giá trị là true, bạn nên bắt đầu quan sát đối tượng LiveData hiện có trong installMonitor.status. Đối tượng LiveData này tạo ra nội dung cập nhật SplitInstallSessionState từ thư viện Play Core. Những nội dung cập nhật này chứa các sự kiện tiến trình cài đặt mà bạn có thể sử dụng để cập nhật giao diện người dùng. Hãy nhớ xử lý mọi trạng thái liên quan như đã nêu trong hướng dẫn Play Core, bao gồm yêu cầu người dùng xác nhận nếu cần thiết.

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

Khi quá trình cài đặt kết thúc, đối tượng LiveData sẽ tạo ra trạng thái SplitInstallSessionStatus.INSTALLED. Sau đó, bạn phải gọi lại hàm NavController.navigate(). Vì giờ đây mô-đun đã được cài đặt nên lệnh gọi sẽ thành công và ứng dụng sẽ điều hướng đến đích như mong đợi.

Sau khi đạt đến trạng thái kết thúc, ví dụ như khi cài đặt xong hoặc khi cài đặt không thành công, bạn nên xóa bộ quan sát LiveData để tránh bị rò rỉ bộ nhớ. Có thể kiểm tra xem trạng thái hiện tại có phải là trạng thái kết thúc hay không bằng cách dùng SplitInstallSessionStatus.hasTerminalStatus().

Hãy xem AbstractProgressFragment để biết ví dụ về cách triển khai trình quan sát này.

Biểu đồ đi kèm

Thư viện Dynamic Navigator hỗ trợ cả biểu đồ được xác định trong các mô-đun tính năng. Để sử dụng một biểu đồ được xác định trong mô-đun tính năng, hãy làm như sau:

  1. Sử dụng <include-dynamic/> thay vì <include/>, như trong ví dụ sau:

    <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. Bạn phải chỉ định các thuộc tính sau bên trong <include-dynamic ... />:

    • app:graphResName: tên của tệp tài nguyên biểu đồ điều hướng. Tên lấy từ tên tệp của biểu đồ. Ví dụ: nếu biểu đồ nằm trong res/navigation/nav_graph.xml, thì tên tài nguyên sẽ là nav_graph.
    • android:id – ID đích đến của biểu đồ. Thư viện Dynamic Navigator bỏ qua mọi giá trị android:id được tìm thấy trong phần tử gốc của biểu đồ đi kèm.
    • app:moduleName: tên gói của mô-đun.

Sử dụng đúng graphPackage

Bạn phải lấy đúng app:graphPackage, nếu không thành phần Điều hướng sẽ không thể bao gồm navGraph được chỉ định từ mô-đun tính năng.

Tên gói của một mô-đun tính năng linh hoạt được tạo bằng cách thêm tên của mô-đun vào applicationId của mô-đun ứng dụng cơ sở. Vì vậy, nếu mô-đun ứng dụng cơ sở có applicationId trêncom.example.dynamicfeatureapp và mô-đun tính năng linh hoạt được đặt tên là DynamicFeatureModule, tên gói của mô-đun linh hoạt sẽ làcom.example.dynamicfeatureapp.DynamicFeatureModule. Tên gói này có phân biệt chữ hoa và chữ thường.

Nếu không chắc chắn, bạn có thể xác nhận tên gói của mô-đun tính năng bằng cách kiểm tra AndroidManifest.xml được tạo. Sau khi tạo dự án, hãy truy cập <DynamicFeatureModule>/build/intermediates/merged_manifest/debug/AndroidManifest.xml, mã này có dạng như sau:

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

Giá trị featureSplit phải khớp với tên của mô-đun tính năng linh hoạt, còn gói sẽ khớp với applicationId của mô-đun ứng dụng cơ sở. app:graphPackage là sự kết hợp của các giá trị này: com.example.dynamicfeatureapp.DynamicFeatureModule.

Bạn chỉ có thể chuyển đến startDestination của biểu đồ điều hướng include-dynamic. Mô-đun linh hoạt chịu trách nhiệm về biểu đồ điều hướng của riêng nó và ứng dụng cơ sở không biết được thông tin này.

Cơ chế linh hoạt cho phép mô-đun ứng dụng cơ sở liên kết biểu đồ điều hướng lồng nhau được xác định trong mô-đun linh hoạt. Biểu đồ điều hướng lồng nhau này cũng hoạt động giống các biểu đồ điều hướng lồng nhau khác. Biểu đồ điều hướng gốc (tức là biểu đồ gốc của biểu đồ điều hướng lồng nhau) chỉ có thể xác định chính biểu đồ điều hướng lồng nhau dưới dạng một đích đến chứ không phải là đích đến con của biểu đồ đó. Do đó, startDestination được sử dụng khi biểu đồ điều hướng linh hoạt là đích đến.

Hạn chế

  • Biểu đồ động hiện không hỗ trợ liên kết sâu.
  • Biểu đồ lồng nhau chủ động tải linh hoạt (nghĩa là phần tử <navigation>app:moduleName) hiện không hỗ trợ liên kết sâu.