Naviguer avec des modules de fonctionnalités

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

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

    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.

deux écrans de chargement qui affichent une barre de progression lorsque vous accédez à un module de fonctionnalité pour la première fois
Figure 1 : Interface utilisateur affichant une barre de progression lorsqu'un utilisateur accède à une fonctionnalité à la demande pour la première fois. L'application affiche cet écran pendant le téléchargement du module correspondant.

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.

écran qui affiche une barre de navigation inférieure avec une icône indiquant qu&#39;un module de fonctionnalité est en cours de téléchargement
Figure 2 : Écran affichant la progression du téléchargement à partir d'une barre de navigation inférieure.

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'objet LiveData qui se trouve maintenant dans installMonitor.status. Cet objet LiveData propose des mises à jour de SplitInstallSessionState 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 :

  1. 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" />
    
  2. 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 dans res/navigation/nav_graph.xml, le nom de la ressource est nav_graph.
    • android:id : identifiant de destination du graphique. La bibliothèque Dynamic Navigator ignore toutes les valeurs android: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.

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 un app:moduleName) ne sont actuellement pas compatibles avec les liens profonds.