นำทางด้วยโมดูลฟีเจอร์

ไลบรารีของ Dynamic Navigator ขยายฟังก์ชันการทำงานของ คอมโพเนนต์การนำทางของ Jetpack เพื่อทำงานกับปลายทาง ที่กำหนดไว้ใน โมดูลฟีเจอร์ ไลบรารีนี้ยังมีการติดตั้งฟีเจอร์ออนดีมานด์ที่ราบรื่น เมื่อไปยังปลายทางเหล่านี้

ตั้งค่า

หากต้องการรองรับโมดูลฟีเจอร์ ให้ใช้ทรัพยากร Dependency ต่อไปนี้ในไฟล์ build.gradle ของโมดูลแอป

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

โปรดทราบว่าทรัพยากร Dependency อื่นๆ ของการนำทางควรใช้การกำหนดค่า 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"
    ... />

จากนั้น เพิ่มแอตทริบิวต์ app:moduleName ลงใน <activity>, <fragment> หรือ จุดหมาย <navigation> แห่งในโมดูล com.android.dynamic-feature กราฟการนำทางที่เชื่อมโยงกับ DynamicNavHostFragment แอตทริบิวต์นี้จะบอกไลบรารี Dynamic Navigator ว่าปลายทาง เป็นของโมดูลฟีเจอร์ที่มีชื่อที่คุณระบุ

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

เมื่อคุณนำทางไปยังจุดหมายเหล่านี้ ไลบรารีการนำทางแบบไดนามิก ก่อนอื่นให้ตรวจสอบว่ามีการติดตั้งโมดูลฟีเจอร์หรือไม่ หากฟีเจอร์ อยู่แล้ว แอปของคุณจะนำทางไปยังปลายทางตามที่คาดไว้ หากไม่มีโมดูลนี้ แสดงว่าแอปแสดงส่วนย่อยของความคืบหน้าระดับกลาง ปลายทางขณะติดตั้งโมดูล การใช้งานเริ่มต้นของ ส่วนความคืบหน้าจะแสดง UI พื้นฐานพร้อมแถบความคืบหน้าและจัดการ ข้อผิดพลาดในการติดตั้ง

วันที่ หน้าจอโหลด 2 หน้าจอที่แสดง UI พร้อมแถบความคืบหน้าขณะนำทาง
         เป็นโมดูลฟีเจอร์เป็นครั้งแรก
รูปที่ 1 UI แสดงแถบความคืบหน้าเมื่อผู้ใช้ไปยังส่วนต่างๆ ไปยังฟีเจอร์แบบออนดีมานด์เป็นครั้งแรก แอปจะแสดงหน้าจอนี้เป็น โมดูลที่เกี่ยวข้องก็จะดาวน์โหลด

หากต้องการปรับแต่ง UI นี้ หรือจัดการการติดตั้งด้วยตนเอง ความคืบหน้าจากภายในหน้าจอแอปของคุณ โปรดดู ปรับแต่งส่วนย่อยความคืบหน้า และ ติดตามสถานะของคำขอในหัวข้อนี้

ปลายทางที่ไม่ได้ระบุ app:moduleName จะยังคงทำงานต่อไปหากไม่มี เปลี่ยนแปลงและทํางานเสมือนว่าแอปของคุณใช้ NavHostFragment ปกติ

ปรับแต่งส่วนย่อยความคืบหน้า

คุณลบล้างการใช้งานส่วนความคืบหน้าสําหรับกราฟการนำทางแต่ละรายการได้ โดยการตั้งค่าแอตทริบิวต์ app:progressDestination เป็นรหัสของปลายทาง ที่ต้องการใช้เพื่อจัดการความคืบหน้าของการติดตั้ง ความคืบหน้าที่กำหนดเอง ปลายทางควรเป็น Fragment ที่มาจาก AbstractProgressFragment คุณต้องลบล้างเมธอด Abstract สำหรับการแจ้งเตือนเกี่ยวกับการติดตั้ง ความคืบหน้า ข้อผิดพลาด และเหตุการณ์อื่นๆ จากนั้นคุณจะแสดงความคืบหน้าในการติดตั้งได้ใน UI ที่ต้องการ

การติดตั้งใช้งานเริ่มต้น DefaultProgressFragment ใช้ API นี้เพื่อแสดงความคืบหน้าในการติดตั้ง

ตรวจสอบสถานะของคำขอ

ไลบรารี Dynamic Navigator ช่วยให้คุณสามารถใช้ขั้นตอน UX ที่คล้ายกับ 1 ใน แนวทางปฏิบัติแนะนำเกี่ยวกับ UX สำหรับการนำส่งแบบออนดีมานด์ ซึ่งผู้ใช้อยู่ในบริบทของหน้าจอก่อนหน้าขณะที่รอ ให้เสร็จสิ้น คุณจึงไม่จำเป็นต้องแสดงตัวกลาง UI หรือส่วนย่อยของความคืบหน้าเลย

วันที่ ซึ่งแสดงแถบนำทางด้านล่างพร้อมด้วยไอคอนที่แสดงถึง
         โมดูลฟีเจอร์กำลังดาวน์โหลด
รูปที่ 2 หน้าจอแสดงความคืบหน้าในการดาวน์โหลดจาก แถบนำทางด้านล่าง

ในกรณีนี้ คุณจะต้องรับผิดชอบ การตรวจสอบและจัดการสถานะการติดตั้งทั้งหมด การเปลี่ยนแปลงความคืบหน้า ข้อผิดพลาด เป็นต้น

ในการเริ่มต้นขั้นตอนการนำทางแบบไม่บล็อกนี้ ให้ส่ง DynamicExtras ที่มีแอตทริบิวต์ DynamicInstallMonitor ถึง 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 คุณควรเริ่มสังเกตออบเจ็กต์ LiveData ที่ ขณะนี้อยู่ใน installMonitor.status ออบเจ็กต์ LiveData นี้ปล่อย SplitInstallSessionState จากไลบรารี Play Core การอัปเดตเหล่านี้มีการติดตั้ง เหตุการณ์ความคืบหน้าที่คุณสามารถใช้เพื่ออัปเดต 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 สำหรับตัวอย่างการใช้งานของผู้สังเกตการณ์รายนี้

กราฟที่มี

ไลบรารี Dynamic Navigator สนับสนุนการรวมกราฟที่กำหนดไว้ใน โมดูลฟีเจอร์ เพื่อรวมกราฟที่กำหนดไว้ในฟีเจอร์ ให้ทำดังนี้

  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 - รหัสปลายทางของกราฟ ไลบรารี Dynamic Navigator ไม่สนใจค่า android:id ที่พบในองค์ประกอบรูทของ กราฟที่รวมไว้
    • app:moduleName: ชื่อแพ็กเกจของโมดูล

ใช้ GrapPackage ที่ถูกต้อง

คุณต้องทำให้ app:graphPackage ถูกต้องในฐานะการนำทาง คอมโพเนนต์จะไม่สามารถรวม navGraph ที่ระบุจากฟีเจอร์ ไม่เช่นนั้น

ชื่อแพ็กเกจของโมดูลฟีเจอร์แบบไดนามิกสร้างขึ้นโดยการเพิ่ม ชื่อของโมดูลเป็น applicationId ของโมดูลแอปฐาน ดังนั้น หาก โมดูลแอปฐานมี applicationId เป็น com.example.dynamicfeatureapp และ โมดูลฟีเจอร์แบบไดนามิกมีชื่อว่า 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

สามารถไปที่ startDestination ของ กราฟการนำทาง include-dynamic โดยโมดูลแบบไดนามิกจะทำหน้าที่ กราฟการนำทางของตนเอง และแอปหลักก็ไม่มีความรู้ด้านนี้เช่นกัน

กลไกการรวมแบบไดนามิกช่วยให้โมดูลแอปพื้นฐานรวม กราฟการนำทางที่ซ้อนกัน ที่กำหนดภายในโมดูลแบบไดนามิก กราฟการนำทางที่ฝังนี้จะทำงาน เหมือนกราฟการนำทางที่ฝังไว้ กราฟการนำทางระดับรูท (ซึ่งก็คือระดับบนสุด ของกราฟเชิงซ้อน) สามารถกำหนดได้เฉพาะกราฟการนำทางที่ซ้อนกันเป็น ปลายทาง ไม่ใช่รายการย่อย ดังนั้นระบบจึงใช้ startDestination เมื่อ กราฟรวมการนำทางแบบไดนามิกคือปลายทาง

ข้อจำกัด

  • กราฟที่รวมแบบไดนามิกยังไม่รองรับลิงก์ในรายละเอียดในขณะนี้
  • กราฟแบบซ้อนที่โหลดแบบไดนามิก (กล่าวคือ องค์ประกอบ <navigation> ที่มี app:moduleName) ยังไม่รองรับ Deep Link ในขณะนี้