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

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

ضبط إعدادات

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

رائع

dependencies {
    def nav_version = "2.7.7"

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

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

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