La bibliothèque Dynamic Navigator étend la fonctionnalité du composant Navigation de Jetpack pour qu'elle fonctionne avec les destinations définies dans les modules de fonctionnalité. Cette bibliothèque permet également d'installer facilement des modules de fonctionnalité à la demande lorsque vous accédez à ces destinations.
Configurer
Pour accepter les modules de fonctionnalité, utilisez les dépendances suivantes dans le fichier build.gradle
de votre module d'application :
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") }
Notez que les autres dépendances Navigation doivent utiliser des configurations d'API. afin qu'ils soient disponibles pour vos modules de fonctionnalité.
Utilisation de base
Pour accepter les modules de fonctionnalités, commencez par remplacer toutes les instances de NavHostFragment
de votre application par 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"
... />
Ensuite, ajoutez un attribut de app:moduleName
à n'importe quelle destination <activity>
, <fragment>
ou <navigation>
dans vos graphiques de module de navigation com.android.dynamic-feature
associés à un DynamicNavHostFragment
.
Cet attribut indique à la bibliothèque Dynamic Navigator que la destination appartient à un module de fonctionnalité portant le nom que vous indiquez.
<fragment
app:moduleName="myDynamicFeature"
android:id="@+id/featureFragment"
android:name="com.google.android.samples.feature.FeatureFragment"
... />
Lorsque vous accédez à l'une de ces destinations, la bibliothèque Dynamic Navigator vérifie d'abord si le module de fonctionnalité est installé. Si le module de fonctionnalité est installé, votre application accède à la destination comme prévu. Si le module n'est pas installé, votre application affiche une destination de fragment de progression intermédiaire lorsqu'elle installe le module. L'implémentation par défaut du fragment de progression affiche une interface utilisateur de base avec une barre de progression et gère les erreurs d'installation.
Pour personnaliser cette interface utilisateur ou pour gérer manuellement la progression de l'installation depuis l'écran de votre propre application, consultez les sections Personnaliser le fragment de progression et Surveiller l'état de la requête de cette rubrique.
Les destinations qui ne spécifient pas app:moduleName
continuent de fonctionner sans modification et se comportent comme si votre application utilisait un NavHostFragment
standard.
Personnaliser le fragment de progression
Vous pouvez remplacer l'implémentation du fragment de progression pour chaque graphique de navigation en définissant l'attribut app:progressDestination
sur l'ID de la destination que vous souhaitez utiliser pour gérer la progression de l'installation. Votre destination de progression personnalisée doit être un Fragment
dérivé de AbstractProgressFragment
.
Vous devez ignorer les méthodes abstraites de notification concernant la progression de l'installation, les erreurs et d'autres événements. Vous pouvez alors afficher la progression de l'installation dans l'interface utilisateur de votre choix.
La classe DefaultProgressFragment
d'implémentation par défaut utilise cette API pour afficher la progression de l'installation.
Surveiller l'état de la requête
La bibliothèque Dynamic Navigator vous permet d'implémenter une expérience utilisateur semblable à celle de la section Bonnes pratiques concernant l'expérience utilisateur pour la diffusion à la demande, dans laquelle un utilisateur reste sur un écran jusqu'à la fin de l'installation. Cela signifie que vous n'avez pas besoin d'afficher une interface utilisateur intermédiaire ni un fragment de progression.
Dans ce cas de figure, vous êtes responsable de la surveillance et de la gestion de tous les états d'installation, des changements de progression, des erreurs, etc.
Pour initier ce flux de navigation non bloquant, transmettez un objet DynamicExtras
contenant un objet DynamicInstallMonitor
à NavController.navigate()
, comme illustré dans l'exemple suivant :
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); )
Immédiatement après l'appel de navigate()
, vous devez vérifier la valeur de installMonitor.isInstallRequired
pour voir si la tentative de navigation a abouti à l'installation du module de fonctionnalité.
- Une valeur de
false
signifie que vous accédez à une destination normale et que vous n'avez rien d'autre à faire. Une valeur de
true
signifie que vous devez commencer à observer l'objetLiveData
qui se trouve maintenant dansinstallMonitor.status
. Cet objetLiveData
propose des mises à jour deSplitInstallSessionState
depuis la bibliothèque Play Core. Ces mises à jour contiennent des événements de progression de l'installation que vous pouvez utiliser pour mettre à jour l'interface utilisateur. N'oubliez pas de gérer tous les états pertinents décrits dans le guide Play Core, y compris la demande de confirmation de l'utilisateur si nécessaire.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); } } }); }
Une fois l'installation terminée, l'objet LiveData
affiche un état SplitInstallSessionStatus.INSTALLED
. Vous devez ensuite rappeler NavController.navigate()
. Maintenant que le module est installé, l'appel aboutit, et l'application accède à la destination comme prévu.
Une fois un état final atteint, par exemple à la fin de l'installation ou en cas d'échec de l'installation, vous devez supprimer l'observateur LiveData
pour éviter les fuites de mémoire. Vous pouvez vérifier si l'état représente un état final à l'aide de SplitInstallSessionStatus.hasTerminalStatus()
.
Pour voir un exemple d'implémentation de cet observateur, consultez AbstractProgressFragment
.
Graphiques inclus
La bibliothèque Dynamic Navigator permet d'inclure des graphiques définis dans les modules de fonctionnalité. Pour inclure un graphique défini dans un module de fonctionnalité, procédez comme suit :
Utilisez
<include-dynamic/>
au lieu de<include/>
, comme dans l'exemple suivant :<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" />
Dans
<include-dynamic ... />
, vous devez spécifier les attributs suivants :app:graphResName
: nom du fichier de ressources du graphique de navigation. Ce nom est dérivé du nom du fichier du graphique. Par exemple, si le graphique se trouve dansres/navigation/nav_graph.xml
, le nom de la ressource estnav_graph
.android:id
: identifiant de destination du graphique. La bibliothèque Dynamic Navigator ignore toutes les valeursandroid:id
trouvées dans l'élément racine du graphique inclus.app:moduleName
: nom du package du module.
Utiliser le bon package graphique
Il est important d'obtenir le app:graphPackage
correct, sinon le composant Navigation ne pourra pas inclure le navGraph
indiqué à partir du module de fonctionnalité.
Le nom de package d'un module de fonctionnalité dynamique est formé en ajoutant le nom du module au applicationId
du module d'application de base. Si le module d'application de base dispose d'un applicationId
de com.example.dynamicfeatureapp
et que le module de fonctionnalité dynamique est nommé DynamicFeatureModule
, le nom du package du module dynamique sera donc com.example.dynamicfeatureapp.DynamicFeatureModule
. Ce nom de package est sensible à la casse.
En cas de doute, vous pouvez confirmer le nom du package du module de fonctionnalité en vérifiant le AndroidManifest.xml
généré. Après avoir créé le projet, accédez à <DynamicFeatureModule>/build/intermediates/merged_manifest/debug/AndroidManifest.xml
, qui devrait ressembler à ceci :
<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>
La valeur du featureSplit
doit correspondre au nom du module de fonctionnalité dynamique, et le package doit correspondre au applicationId
du module d'application de base. app:graphPackage
est la combinaison de ces éléments : com.example.dynamicfeatureapp.DynamicFeatureModule
.
Accéder à un graphique de navigation d'inclusion dynamique
Il n'est possible d'accéder qu'au startDestination
d'un graphique de navigation include-dynamic
. Le module dynamique est responsable de son propre graphique de navigation, dont l'application de base n'a pas connaissance.
Le mécanisme d'inclusion dynamique permet au module d'application de base d'inclure un graphique de navigation imbriqué défini dans le module dynamique. Ce graphique de navigation imbriqué se comporte comme n'importe quel graphique de navigation imbriqué. Le graphique de navigation racine (c'est-à-dire le
du graphique imbriqué) ne peut définir le graphique de navigation imbriqué qu'en tant que
et non ses enfants. Le startDestination
est donc utilisé lorsque le graphique d'inclusion dynamique est la destination.
Restrictions
- Les graphiques inclus dynamiquement ne sont actuellement pas compatibles avec les liens profonds.
- Les graphiques imbriqués chargés dynamiquement (c'est-à-dire, un élément
<navigation>
avec unapp:moduleName
) ne sont actuellement pas compatibles avec les liens profonds.