پیمایش با واحدهای ویژگی

کتابخانه Dynamic Navigator عملکرد مؤلفه Jetpack Navigation را گسترش می‌دهد تا با مقصدهایی که در واحدهای ویژگی تعریف شده‌اند کار کند. این کتابخانه همچنین نصب یکپارچه واحدهای ویژگی درخواستی را هنگام پیمایش به این مقصدها فراهم می‌کند.

راه‌اندازی

برای پشتیبانی از واحدهای ویژگی، از وابستگی‌های زیر در فایل build.gradle واحد برنامه‌تان استفاده کنید:

گرووی

dependencies {
    def nav_version = "2.10.2"

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

کاتلین

dependencies {
    val nav_version = "2.10.2"

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

توجه داشته باشید که سایر وابستگی‌های Navigation باید از پیکربندی‌های 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"
    ... />

وقتی به یکی از این مقصدها پیمایش می‌کنید، کتابخانه Dynamic Navigator ابتدا بررسی می‌کند که واحد ویژگی نصب شده است یا نه. اگر واحد ویژگی ازقبل وجود داشته باشد، برنامه‌تان طبق انتظار به مقصد پیمایش می‌کند. اگر ماژول وجود نداشته باشد، برنامه‌تان هنگام نصب ماژول، مقصد قطعه پیشرفت میانی را نشان می‌دهد. پیاده‌سازی پیش‌فرض تکه پیشرفت، میانای کاربری پایه‌ای را با نوار پیشرفت نشان می‌دهد و هرگونه خطای نصب را مدیریت می‌کند.

دو صفحه بارگیری که میانای کاربر را با نوار پیشرفت هنگام پیمایش
         به واحد ویژگی برای اولین‌بار نشان می‌دهد
شکل ۱. واسط کاربری که نوار پیشرفت را هنگام پیمایش کاربر به ویژگی درخواستی برای اولین‌بار نشان می‌دهد. برنامه این صفحه را هنگام بارگیری واحد مربوطه نمایش می‌دهد.

برای سفارشی‌سازی این میانای کاربر، یا برای مدیریت دستی پیشرفت نصب از داخل صفحه برنامه خودتان، بخش‌های سفارشی‌سازی قطعه پیشرفت و پایش وضعیت درخواست را در این موضوع ببینید.

مقصدهایی که app:moduleName را مشخص نمی‌کنند همچنان بدون تغییر کار می‌کنند و طوری رفتار می‌کنند که گویی برنامه شما از NavHostFragment معمولی استفاده می‌کند.

سفارشی‌سازی قطعه پیشرفت

می‌توانید پیاده‌سازی قطعه پیشرفت را برای هر نمودار پیمایش لغو کنید با تنظیم کردن ویژگی app:progressDestination روی شناسه مقصدی که می‌خواهید برای رسیدگی به پیشرفت نصب استفاده کنید. مقصد پیشرفت سفارشی شما باید Fragment باشد که از AbstractProgressFragment مشتق شده است. باید روش‌های انتزاعی را برای اعلان‌های مربوط به پیشرفت نصب، خطاها، و رویدادهای دیگر لغو کنید. سپس می‌توانید پیشرفت نصب را در رابط کاربری انتخابی‌تان نشان دهید.

کلاس DefaultProgressFragment پیاده‌سازی پیش‌فرض از این میانای برنامه‌سازی کاربردی برای نشان دادن پیشرفت نصب استفاده می‌کند.

پایش کردن وضعیت درخواست

کتابخانه Dynamic Navigator به شما امکان می‌دهد جریان تجربه کاربری مشابه با جریان موجود در روال‌های مطلوب تجربه کاربری برای تحویل درخواستی را پیاده‌سازی کنید، که در آن کاربر درحین انتظار برای تکمیل نصب، در بافت صفحه قبلی باقی می‌ماند. این یعنی اصلاً لازم نیست واسط کاربری میانی یا قطعه پیشرفت را نشان دهید.

صفحه‌ای که نوار پیمایش پایینی را با نمادی نشان می‌دهد که
         بارگیری واحد ویژگی را نشان می‌دهد
شکل ۲. صفحه‌ای که پیشرفت بارگیری را از نوار پیمایش پایین نشان می‌دهد.

در این سناریو، شما مسئول نظارت و رسیدگی به همه وضعیت‌های نصب، تغییرات پیشرفت، خطاها، و غیره هستید.

برای شروع این جریان پیمایش غیرمسدودکننده، یک شیء DynamicExtras را که حاوی یک DynamicInstallMonitor است به NavController.navigate() ارسال کنید، همان‌طور که در مثال زیر نشان داده شده است:

کاتلین

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

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

جاوا

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» منتشر می‌کند. این به‌روزرسانی‌ها حاوی رویدادهای پیشرفت نصب هستند که می‌توانید از آن‌ها برای به‌روزرسانی میانای کاربر استفاده کنید. به‌خاطر داشته باشید که همه وضعیت‌های مربوطه را همان‌طور که در راهنمای Play Core ذکر شده است، ازجمله درخواست تأیید کاربر درصورت لزوم، مدیریت کنید.

    کاتلین

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

    جاوا

    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 observer را بردارید تا از نشت حافظه جلوگیری شود. بااستفاده از SplitInstallSessionStatus.hasTerminalStatus() می‌توانید بررسی کنید که آیا وضعیت نشان‌دهنده حالت پایانه است یا نه.

برای نمونه‌ای از پیاده‌سازی این ناظر، AbstractProgressFragment را ببینید.

نمودارهای موجود

کتابخانه Dynamic Navigator از گنجاندن نمودارهایی که در واحدهای ویژگی تعریف شده‌اند پشتیبانی می‌کند. برای گنجاندن نموداری که در واحد ویژگی تعریف شده است، کارهای زیر را انجام دهید:

  1. به‌جای <include/> از <include-dynamic/> استفاده کنید، همان‌طور که در مثال زیر نشان داده شده است:

    <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: نام بسته واحد.

از graphPackage صحیح استفاده کنید

مهم است که 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 نمودار ناوبری پیمایش کرد. واحد پویا مسئول نمودار ناوبری خودش است و برنامه پایه هیچ اطلاعی از آن ندارد.

سازوکار include-dynamic به ماژول برنامه پایه امکان می‌دهد تا نمودار ناوبری تودرتو را که در ماژول پویا تعریف شده است، شامل شود. این گراف ناوبری تودرتو مانند هر گراف ناوبری تودرتوی دیگری رفتار می‌کند. نمودار پیمایش ریشه (یعنی والد نمودار تودرتو) فقط می‌تواند خود نمودار پیمایش تودرتو را به‌عنوان مقصد تعریف کند و نه فرزندان آن را. بنابراین، وقتی گراف include-dynamicnavigation مقصد باشد، از startDestination استفاده می‌شود.

محدودیت‌ها

  • درحال‌حاضر نمودارهای گنجانده‌شده به‌صورت پویا از پیوندهای عمیق پشتیبانی نمی‌کنند.
  • نمودارهای تودرتوی بارشده به‌صورت پویا (یعنی عنصر <navigation> با app:moduleName) درحال‌حاضر از پیوندهای عمیق پشتیبانی نمی‌کنند.