Навигация с функциональными модулями,Навигация с функциональными модулями

Библиотека Dynamic Navigator расширяет функциональность компонента Jetpack Navigation для работы с пунктами назначения, определенными в функциональных модулях . Эта библиотека также обеспечивает плавную установку функциональных модулей по требованию при переходе к этим местам назначения.

Настраивать

Для поддержки функциональных модулей используйте следующие зависимости в файле build.gradle вашего модуля приложения:

Groovy

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

Обратите внимание, что другие зависимости навигации должны использовать конфигурации 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"
    ... />

Когда вы переходите к одному из этих пунктов назначения, библиотека динамического навигатора сначала проверяет, установлен ли функциональный модуль. Если функциональный модуль уже присутствует, ваше приложение перейдет к месту назначения, как и ожидалось. Если модуль отсутствует, ваше приложение отображает место назначения промежуточного фрагмента хода выполнения при установке модуля. Реализация фрагмента выполнения по умолчанию отображает базовый пользовательский интерфейс с индикатором выполнения и обрабатывает любые ошибки установки.

два экрана загрузки, на которых отображается пользовательский интерфейс с индикатором выполнения при первом переходе к функциональному модулю
Рис. 1. Пользовательский интерфейс, показывающий индикатор выполнения, когда пользователь впервые переходит к функции по требованию. Приложение отображает этот экран при загрузке соответствующего модуля.

Чтобы настроить этот пользовательский интерфейс или вручную управлять ходом установки на экране собственного приложения, см. разделы «Настройка фрагмента выполнения» и «Отслеживание состояния запроса» в этом разделе.

Назначения, которые не указывают app:moduleName продолжают работать без изменений и ведут себя так, как будто ваше приложение использует обычный NavHostFragment .

Настройте фрагмент прогресса

Вы можете переопределить реализацию фрагмента прогресса для каждого графа навигации, задав для атрибута app:progressDestination идентификатор места назначения, которое вы хотите использовать для обработки хода установки. Вашим пользовательским местом назначения прогресса должен быть Fragment , производный от AbstractProgressFragment . Необходимо переопределить абстрактные методы для уведомлений о ходе установки, ошибках и других событиях. Затем вы можете отобразить ход установки в пользовательском интерфейсе по вашему выбору.

Класс DefaultProgressFragment реализации по умолчанию использует этот API для отображения хода установки.

Следите за состоянием запроса

Библиотека динамического навигатора позволяет реализовать поток пользовательского интерфейса, аналогичный описанному в лучших практиках пользовательского интерфейса для доставки по требованию , в котором пользователь остается в контексте предыдущего экрана, ожидая завершения установки. Это означает, что вам вообще не нужно показывать промежуточный пользовательский интерфейс или фрагмент прогресса.

экран, на котором отображается нижняя панель навигации со значком, указывающим на загрузку функционального модуля.
Рис. 2. Экран, показывающий ход загрузки на нижней панели навигации.

В этом сценарии вы несете ответственность за мониторинг и обработку всех состояний установки, изменений хода выполнения, ошибок и т. д.

Чтобы инициировать этот неблокирующий поток навигации, передайте объект 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 Core. Эти обновления содержат события хода установки, которые можно использовать для обновления пользовательского интерфейса. Не забудьте обрабатывать все соответствующие статусы, как описано в руководстве 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 , чтобы избежать утечек памяти. Вы можете проверить, представляет ли статус состояние терминала, используя SplitInstallSessionStatus.hasTerminalStatus() .

См. AbstractProgressFragment для примера реализации этого наблюдателя.

Включенные графики

Библиотека динамического навигатора поддерживает включение графиков, определенных в функциональных модулях. Чтобы включить график, определенный в функциональном модуле, выполните следующие действия:

  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 навигации. Динамический модуль отвечает за собственный граф навигации, о котором базовое приложение ничего не знает.

Механизм include-dynamic позволяет базовому модулю приложения включать вложенный граф навигации , определенный в динамическом модуле. Этот вложенный граф навигации ведет себя как любой вложенный граф навигации. Корневой граф навигации (то есть родительский элемент вложенного графа) может определять только сам вложенный граф навигации как пункт назначения, а не его дочерние элементы. Таким образом, startDestination используется, когда пунктом назначения является граф include-dynamicnavigation.

Ограничения

  • Динамически включенные графики в настоящее время не поддерживают глубокие ссылки.
  • Динамически загружаемые вложенные графики (то есть элемент <navigation> с app:moduleName ) в настоящее время не поддерживают глубокие ссылки.