التنقّل باستخدام وحدات الميزات

تعمل مكتبة Dynamic Navigator على توسيع نطاق وظائف مكوِّن التنقّل في Jetpack للعمل مع الوجهات التي يتم تحديدها في وحدات الميزات: توفّر هذه المكتبة أيضًا إمكانية تثبيت الميزات المتوفرة عند الطلب بسهولة. الوحدات عند الانتقال إلى هذه الوجهات.

ضبط إعدادات الجهاز

لدعم وحدات الميزات، استخدِم الاعتماديات التالية في ملف build.gradle الخاص بوحدة تطبيقك:

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

لاحظ أن اعتماديات التنقّل الأخرى يجب أن تستخدم عمليات ضبط واجهة برمجة التطبيقات. لكي تكون متاحة لوحدات الميزات لديك.

الاستخدام الأساسي

لدعم وحدات الميزات، غيِّر أولاً جميع مثيلات 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. يتحقق أولاً من تثبيت وحدة الميزات. إذا كانت الميزة موجودة بالفعل، ينتقل تطبيقك إلى الوجهة على النحو المتوقع. إذا لم تكن الوحدة متوفّرة، سيعرض تطبيقك جزءًا متوسط مستوى التقدّم. جديدة أثناء تثبيت الوحدة. يُعد التنفيذ الافتراضي يعرض جزء التقدم واجهة مستخدم أساسية مع شريط تقدم ويتعامل مع أي أخطاء التثبيت.

شاشتا تحميل تعرضان واجهة مستخدم مع شريط تقدّم عند التنقّل
         إلى وحدة ميزة لأول مرة
الشكل 1. واجهة مستخدم تعرض شريط التقدّم عندما يتنقل مستخدم على ميزة عند الطلب لأول مرة. يعرض التطبيق هذه الشاشة في حال تنزيل الوحدات المعنيّة.

لتخصيص واجهة المستخدم هذه أو معالجة عملية التثبيت يدويًا التقدم من داخل شاشة التطبيق، فاطلع على تخصيص جزء مستوى التقدّم مراقبة أقسام حالة الطلب في هذا الموضوع

تواصل الوجهات التي لا تُحدِّد app:moduleName العمل بدونها. يتغير ويتصرف كما لو كان تطبيقك يستخدم NavHostFragment.

تخصيص جزء مستوى التقدّم

يمكنك إلغاء تنفيذ جزء مستوى التقدم لكل رسم بياني للتنقل من خلال ضبط السمة app:progressDestination على رقم تعريف الوجهة الذي تريد استخدامه للتعامل مع تقدم التثبيت. مستوى تقدّمك المخصّص الوجهة يجب أن تكون Fragment التي تنشأ من AbstractProgressFragment عليك إلغاء الطرق المجرّدة للإشعارات المتعلّقة بالتثبيت. والتقدم والأخطاء والأحداث الأخرى. ويمكنك بعد ذلك إظهار مستوى تقدم التثبيت واجهة مستخدم من اختيارك

تعتمد طريقة التنفيذ التلقائية DefaultProgressFragment تستخدم هذه الفئة واجهة برمجة التطبيقات هذه لعرض مستوى تقدُّم عملية التثبيت.

مراقبة حالة الطلب

تتيح لك مكتبة Dynamic Navigator تنفيذ تدفق تجربة مستخدم مشابه واحد من كل أفضل ممارسات تجربة المستخدم للتسليم عند الطلب يظل فيها المستخدم في سياق شاشة سابقة أثناء انتظار التثبيت لإنهاء العمل. وهذا يعني أنك لست بحاجة إلى عرض رمز متوسط واجهة المستخدم أو جزء التقدم على الإطلاق.

تعرض شريط تنقل سفليًا مع رمز يشير إلى
         الذي يتم من خلاله تنزيل وحدة ميزة
الشكل 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 من بينها طلب تأكيد من المستخدم إذا لزم الأمر.

    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: اسم حزمة الوحدة

استخدام حزم الرسوم البيانية الصحيحة

من المهم الحصول على 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) لا تتوافق حاليًا مع الروابط لصفحات في التطبيق.