Библиотека 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"
... />
Когда вы переходите к одному из этих пунктов назначения, библиотека динамического навигатора сначала проверяет, установлен ли функциональный модуль. Если функциональный модуль уже присутствует, ваше приложение перейдет к месту назначения, как и ожидалось. Если модуль отсутствует, ваше приложение отображает место назначения промежуточного фрагмента хода выполнения при установке модуля. Реализация фрагмента выполнения по умолчанию отображает базовый пользовательский интерфейс с индикатором выполнения и обрабатывает любые ошибки установки.
![два экрана загрузки, на которых отображается пользовательский интерфейс с индикатором выполнения при первом переходе к функциональному модулю](https://developer.android.com/static/images/guide/navigation/dfm-nav-loading.png?hl=ru)
Чтобы настроить этот пользовательский интерфейс или вручную управлять ходом установки на экране собственного приложения, см. разделы «Настройка фрагмента выполнения» и «Отслеживание состояния запроса» в этом разделе.
Назначения, которые не указывают app:moduleName
продолжают работать без изменений и ведут себя так, как будто ваше приложение использует обычный NavHostFragment
.
Настройте фрагмент прогресса
Вы можете переопределить реализацию фрагмента прогресса для каждого графа навигации, задав для атрибута app:progressDestination
идентификатор места назначения, которое вы хотите использовать для обработки хода установки. Вашим пользовательским местом назначения прогресса должен быть Fragment
, производный от AbstractProgressFragment
. Необходимо переопределить абстрактные методы для уведомлений о ходе установки, ошибках и других событиях. Затем вы можете отобразить ход установки в пользовательском интерфейсе по вашему выбору.
Класс DefaultProgressFragment
реализации по умолчанию использует этот API для отображения хода установки.
Следите за состоянием запроса
Библиотека динамического навигатора позволяет реализовать поток пользовательского интерфейса, аналогичный описанному в лучших практиках пользовательского интерфейса для доставки по требованию , в котором пользователь остается в контексте предыдущего экрана, ожидая завершения установки. Это означает, что вам вообще не нужно показывать промежуточный пользовательский интерфейс или фрагмент прогресса.
![экран, на котором отображается нижняя панель навигации со значком, указывающим на загрузку функционального модуля.](https://developer.android.com/static/images/guide/navigation/nav-dfm-ui.png?hl=ru)
В этом сценарии вы несете ответственность за мониторинг и обработку всех состояний установки, изменений хода выполнения, ошибок и т. д.
Чтобы инициировать этот неблокирующий поток навигации, передайте объект 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
для примера реализации этого наблюдателя.
Включенные графики
Библиотека динамического навигатора поддерживает включение графиков, определенных в функциональных модулях. Чтобы включить график, определенный в функциональном модуле, выполните следующие действия:
Используйте
<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" />
Внутри
<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
) в настоящее время не поддерживают глубокие ссылки.