ניווט באמצעות מודולים של מאפיינים

הספרייה Dynamic Navigator מרחיב את הפונקציונליות של רכיב הניווט של Jetpack לעבודה עם יעדים שמוגדרות מודולים של תכונות. הספרייה הזו גם מספקת התקנה חלקה של תכונה על פי דרישה מודולים כשמנווטים ליעדים האלה.

הגדרה

כדי לתמוך במודולים של תכונות, צריך להשתמש ביחסי התלות הבאים בקובץ build.gradle של מודול האפליקציה:

מגניב

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

שים לב שיחסי התלות האחרים של הניווט צריכים להשתמש בהגדרות 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. מאפיין זה מציין לספריית הניווט הדינמי שהיעד שייכת למודול תכונה עם השם שציינתם.

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

כשמנווטים לאחד מהיעדים האלה, הספרייה Dynamic Navigator בודק קודם אם מודול התכונות מותקן. אם התכונה המודול כבר קיים, האפליקציה מנווטת ליעד כמצופה. אם המודול לא נמצא, האפליקציה שלך מציגה מקטע של התקדמות ביניים היעד כשהוא מתקין את המודול. הטמעת ברירת המחדל של קטע התקדמות מציג ממשק משתמש בסיסי עם סרגל התקדמות ומטפל בכל במהלך ההתקנה.

שני מסכי טעינה שמציגים ממשק משתמש עם סרגל התקדמות בזמן הניווט
         למודול תכונה, בפעם הראשונה
איור 1. ממשק משתמש שמוצג בו סרגל התקדמות כשמשתמש מנווט לתכונה על פי דרישה בפעם הראשונה. האפליקציה מציגה את המסך הזה בתור ההורדה של המודול המתאים.

כדי להתאים אישית את ממשק המשתמש הזה, או לטפל בהתקנה באופן ידני את ההתקדמות מתוך מסך האפליקציה שלכם, להתאים אישית את מקטע ההתקדמות וגם מעקב אחרי הקטעים של מצב הבקשה בנושא הזה.

יעדים שלא צוין בהם app:moduleName ימשיכו לפעול בלי משתנה ומתנהג כאילו האפליקציה משתמשת ב-NavHostFragment רגיל.

התאמה אישית של מקטע ההתקדמות

אפשר לשנות את ההטמעה של מקטע ההתקדמות בכל תרשים ניווט באמצעות הגדרת המאפיין app:progressDestination כמזהה של היעד שבהן רוצים להשתמש לטיפול בהתקדמות ההתקנה. ההתקדמות בהתאמה אישית היעד צריך להיות Fragment שנגזרת AbstractProgressFragment כדי לקבל התראות על התקנה צריך לשנות את השיטות המופשטות התקדמות, שגיאות ואירועים אחרים. לאחר מכן תוכלו להציג את התקדמות ההתקנה ממשק משתמש לבחירתך.

תבניות ברירת המחדל להטמעה DefaultProgressFragment class משתמש ב-API הזה כדי להציג את התקדמות ההתקנה.

מעקב אחרי מצב הבקשה

הספרייה Dynamic Navigator מאפשרת להטמיע תהליך UX שדומה אחד ב שיטות מומלצות לשיפור חוויית המשתמש למסירה על פי דרישה, שבו המשתמש נשאר בהקשר של מסך קודם בזמן שהוא ממתין עד לסיום ההתקנה. כלומר, אין צורך להציג לחצן ביניים ממשק משתמש או מקטע התקדמות.

מסך שמציג סרגל ניווט תחתון עם סמל שמציין
         שמתבצעת הורדה של מודול תכונה
איור 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. העדכונים האלה כוללים התקנה אירועי התקדמות שאפשר להשתמש בהם כדי לעדכן את ממשק המשתמש. חשוב לזכור לטפל בהכול הרלוונטיים, כפי שמתואר Play Core guide כולל בקשת אישור משתמש במידת הצורך.

    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: שם החבילה של המודול.

צריך להשתמש ב-ChartPackage הנכון

חשוב לוודא שה-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. המודול הדינמי אחראי את תרשים הניווט עצמו, והאפליקציה הבסיסית לא יודעת על זה.

המנגנון הכללה-דינמית מאפשר למודול הבסיסי של האפליקציה לכלול תרשים ניווט מוטמע שמוגדר במודול הדינמי. תרשים הניווט המקונן הזה מתנהג כמו כל תרשים ניווט מקונן. תרשים הניווט ברמה הבסיסית (root) (כלומר ההורה של הגרף המקונן) יכול להגדיר רק את תרשים הניווט הפנימי ולא הצאצאים שלו. לכן, נעשה שימוש ב-startDestination כאשר תרשים הניווט הדינמי-הכללה הוא היעד.

מגבלות

  • תרשימים שנכללים באופן דינמי לא תומכים כרגע בקישורי עומק.
  • גרפים מקוננים שנטענים באופן דינמי (כלומר, רכיב <navigation> עם app:moduleName) לא תומכים כרגע בקישורי עומק.