Cómo navegar con módulos de funciones

La biblioteca de Dynamic Navigator extiende la funcionalidad del componente de Jetpack Navigation para trabajar con destinos definidos en los módulos de funciones. Esta biblioteca también proporciona una instalación sin interrupciones de módulos de funciones a pedido cuando navegas a estos destinos.

Configuración

Para admitir módulos de funciones, usa las siguientes dependencias en el archivo build.gradle del módulo de tu app:

dependencies {
    def nav_version = "2.3.5"

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

Ten en cuenta que las otras dependencias de Navigation deben usar configuraciones de API a fin de que estén disponibles para tus módulos de funciones.

Uso básico

Para admitir módulos de funciones, primero cambia todas las instancias de NavHostFragment en tu app por 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"
    ... />

Luego, agrega un atributo app:moduleName a cualquier destino <activity>, <fragment> o <navigation> en los gráficos de navegación del módulo com.android.dynamic-feature que estén asociados con un objeto DynamicNavHostFragment. Este atributo indicará a la biblioteca de Dynamic Navigator que el destino pertenece a un módulo de funciones con el nombre que especifiques.

<fragment
    app:moduleName="myDynamicFeature"
    android:id="@+id/featureFragment"
    android:name="com.google.android.samples.feature.FeatureFragment"
    ... />

Cuando navegas a uno de estos destinos, la biblioteca de Dynamic Navigator primero comprueba si se instaló el módulo de funciones. Si el módulo de funciones ya está presente, la app navega al destino como se espera. Si el módulo no está presente, la app muestra un destino de fragmento de progreso intermedio mientras instala el módulo. La implementación predeterminada del fragmento de progreso muestra una IU básica con una barra de progreso y controla cualquier error de instalación.

Dos pantallas de carga que muestran la IU con una barra de progreso cuando navegas a un módulo de funciones por primera vez
Figura 1: IU que muestra una barra de progreso cuando un usuario navega a una función a pedido por primera vez La app muestra esta pantalla mientras se descarga el módulo correspondiente.

Para personalizar esta IU o controlar de forma manual el progreso de la instalación desde tu propia pantalla de app, consulta las secciones Cómo personalizar el fragmento de progreso y Cómo supervisar el estado de la solicitud en este tema.

Los destinos que no especifican app:moduleName continúan funcionando sin cambios y se comportan como si tu app usara un NavHostFragment normal.

Personaliza el fragmento de progreso

Puedes anular la implementación del fragmento de progreso para cada gráfico de navegación si configuras el atributo app:progressDestination con el ID del destino que deseas usar para manejar el progreso de la instalación. Tu destino de progreso personalizado debe ser un Fragment que derive de AbstractProgressFragment. Debes anular los métodos abstractos para las notificaciones sobre el progreso de la instalación, los errores y otros eventos. Luego, puedes mostrar el progreso de la instalación en la IU que prefieras.

La clase DefaultProgressFragment de la implementación predeterminada usa esta API para mostrar el progreso de la instalación.

Supervisa el estado de la solicitud

La biblioteca de Dynamic Navigator te permite implementar un flujo de UX similar al de las prácticas recomendadas de UX para la entrega de funciones a pedido, en las que un usuario permanece en el contexto de una pantalla anterior mientras espera que finalice la instalación. Eso significa que no necesitas mostrar una IU intermedia ni un fragmento de progreso.

Pantalla que muestra una barra de navegación inferior con un ícono que indica que se descarga un módulo de funciones
Figura 2: Pantalla que muestra el progreso de la descarga desde la barra de navegación inferior

En esta situación, eres responsable de supervisar y controlar todos los estados de instalación, cambios en el progreso, errores, etcétera.

Para iniciar este flujo de navegación sin bloqueo, pasa un objeto DynamicExtras que contenga un DynamicInstallMonitor a NavController.navigate(), como se muestra en el siguiente ejemplo:

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);
)

Inmediatamente después de llamar a navigate(), debes verificar el valor de installMonitor.isInstallRequired para ver si la navegación que se intentó realizó la instalación de un módulo de funciones.

  • Si el valor es false, estás navegando a un destino normal y no necesitas hacer nada más.
  • Si el valor es true, debes comenzar a observar el objeto LiveData que ahora está en installMonitor.status. Este objeto LiveData emite actualizaciones de SplitInstallSessionState desde la biblioteca de Play Core. Estas actualizaciones contienen eventos de progreso de la instalación que puedes usar para actualizar la IU. Recuerda controlar todos los estados relevantes, como se describe en la guía de Play Core, incluida una solicitud de confirmación del usuario, si es necesario, para crear el adjunto de VLAN de supervisión.

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

Cuando finaliza la instalación, el objeto LiveData emite un estado SplitInstallSessionStatus.INSTALLED. Luego, deberías volver a llamar a NavController.navigate(). Como el módulo ahora está instalado, la llamada ahora se ejecuta correctamente y la app navega al destino según lo previsto.

Después de alcanzar el estado de una terminal, como cuando finaliza la instalación o cuando falla la instalación, debes quitar el observador de LiveData para evitar las pérdidas de memoria. Puedes verificar si el estado representa un estado de la terminal con SplitInstallSessionStatus.hasTerminalStatus().

Consulta AbstractProgressFragment para ver una implementación de ejemplo de este observador.

Gráficos incluidos

La biblioteca de Dynamic Navigator admite incluir gráficos que se definen en módulos de funciones. Para incluir un gráfico definido en un módulo de funciones, haz lo siguiente:

  1. Usa <include-dynamic/> en lugar de <include/>, como se muestra en el siguiente ejemplo:

    <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. Dentro de <include-dynamic ... />, debes especificar los siguientes atributos:

    • app:graphResName: Es el nombre del archivo de recursos del gráfico de navegación. El nombre se deriva del nombre de archivo del gráfico. Por ejemplo, si el gráfico está en res/navigation/nav_graph.xml, el nombre del recurso es nav_graph.
    • android:id: Es el ID del destino del gráfico. La biblioteca de Dynamic Navigator ignora cualquier valor android:id que se encuentre en el elemento raíz del gráfico incluido.
    • app:moduleName: Es el nombre del módulo de funciones.

Limitaciones

  • Por el momento, los gráficos con funciones dinámicas no admiten vínculos directos.
  • Por el momento, los gráficos anidados cargados de forma dinámica (es decir, un elemento <navigation> con app:moduleName) no admiten vínculos directos.

Compatibilidad con Android Studio

Para usar el editor de Navigation con la biblioteca de Dynamic Navigator debes usar la versión 4.0 o posterior de Android Studio.

En versiones anteriores de Android Studio, debes trabajar directamente con el XML del gráfico de navegación. Abrir un gráfico de navegación que contenga cualquiera de las etiquetas o los atributos descritos en este tema podría generar excepciones y otros comportamientos indefinidos.