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:
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") }
Ten en cuenta que las otras dependencias de Navigation deben usar configuraciones de API para 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.
Para personalizar esta IU o controlar de forma manual el progreso de la instalación desde tu propia pantalla de app, consulta las secciones Personaliza el fragmento de progreso y Supervisa 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.
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 objetoLiveData
que ahora está eninstallMonitor.status
. Este objetoLiveData
emite actualizaciones deSplitInstallSessionState
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 la solicitud de confirmación del usuario, si es necesario.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:
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" />
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á enres/navigation/nav_graph.xml
, el nombre del recurso esnav_graph
.android:id
: Es el ID del destino del gráfico. La biblioteca de Dynamic Navigator ignora cualquier valorandroid:id
que se encuentre en el elemento raíz del gráfico incluido.app:moduleName
: Es el nombre del paquete del módulo.
Cómo usar el graphPackage correcto
Es importante obtener el app:graphPackage
correcto, ya que, de lo contrario, el componente Navigation no podrá incluir el navGraph
especificado del módulo de funciones.
El nombre de paquete de un módulo de funciones dinámicas se crea agregando el nombre del módulo al applicationId
del módulo base de la app. Entonces, si el módulo base de la app tiene un applicationId
de com.example.dynamicfeatureapp
y el módulo de funciones dinámicas se llama DynamicFeatureModule
, el nombre del paquete del módulo dinámico será com.example.dynamicfeatureapp.DynamicFeatureModule
. En el nombre del paquete, se distingue entre mayúsculas y minúsculas.
Si tienes dudas, puedes confirmar el nombre del paquete del módulo de funciones consultando el AndroidManifest.xml
generado. Después de compilar el proyecto, ve a <DynamicFeatureModule>/build/intermediates/merged_manifest/debug/AndroidManifest.xml
, que debería tener el siguiente aspecto:
<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>
El valor featureSplit
debe coincidir con el nombre del módulo de funciones dinámicas, y el paquete coincidirá con el applicationId
del módulo de base de la app. La app:graphPackage
es la combinación de los siguientes elementos: com.example.dynamicfeatureapp.DynamicFeatureModule
.
Cómo navegar a un gráfico de navegación include-dynamic
Solo es posible navegar al elemento startDestination
de un gráfico de navegación include-dynamic
. El módulo dinámico es responsable de su propio gráfico de navegación, pero la app de base no lo sabe.
El mecanismo de inclusión dinámica permite que el módulo base de la app incluya un gráfico de navegación anidado que se define dentro del módulo dinámico. Este gráfico de navegación anidado se comporta como cualquier otro de su tipo. El gráfico de navegación raíz (es decir, el superior del gráfico anidado) solo puede definir el gráfico de navegación anidado como un destino, y no sus elementos secundarios. Por lo tanto, startDestination
se usa cuando el gráfico de include-dynamicnavigation es el destino.
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>
conapp:moduleName
) no admiten vínculos directos.