Modulariza el código de navegación

En esta página, se incluye una guía para modularizar tu código de navegación. Su objetivo es complementar la guía general para la modularización de apps.

Descripción general

La modularización del código de navegación es el proceso de separar las claves de navegación relacionadas y el contenido que representan en módulos individuales. Esto proporciona una separación clara de las responsabilidades y te permite navegar entre diferentes funciones de tu app.

Para modularizar tu código de navegación, haz lo siguiente:

  • Crea dos submódulos: api y impl para cada función de tu app.
  • Coloca las claves de navegación de cada función en su módulo api.
  • Coloca entryProviders y el contenido navegable de cada función en el módulo impl asociado.
  • Proporciona entryProviders a los módulos principales de tu app, ya sea directamente o con la inyección de dependencias.

Separa las funciones en submódulos de API y de implementación

Para cada función de tu app, crea dos submódulos llamados api y impl (abreviatura de "implementation"). Usa la siguiente tabla para decidir dónde colocar el código de navegación.

Nombre del módulo

Contiene

api

Claves de navegación

impl

Contenido de esa función, incluidas las definiciones de NavEntrys y el entryProvider. Consulta también cómo resolver claves en contenido.

Este enfoque permite que una función navegue a otra, ya que permite que su contenido, incluido en su módulo impl, dependa de las claves de navegación de otro módulo, incluido en el módulo api de ese módulo.

Diagrama de dependencia del módulo de funciones que muestra cómo los módulos "impl" pueden depender de los módulos "api".
Figura 1. Diagrama de dependencias del módulo de funciones que muestra cómo los módulos de implementación pueden depender de los módulos de API

Separa las entradas de navegación con funciones de extensión

En Navigation 3, el contenido navegable se define con entradas de navegación. Para separar estas entradas en módulos independientes, crea funciones de extensión en EntryProviderScope y muévelas al módulo impl de esa función. Estos se conocen como generadores de entradas.

En el siguiente ejemplo de código, se muestra un generador de entradas que compila dos entradas de navegación.

// import androidx.navigation3.runtime.EntryProviderScope
// import androidx.navigation3.runtime.NavKey

fun EntryProviderScope<NavKey>.featureAEntryBuilder() {
    entry<KeyA> {
        ContentRed("Screen A") {
            // Content for screen A
        }
    }
    entry<KeyA2> {
        ContentGreen("Screen A2") {
            // Content for screen A2
        }
    }
}

Llama a esa función con el entryProvider DSL cuando definas tu entryProvider en el módulo principal de la app.

// import androidx.navigation3.runtime.entryProvider
// import androidx.navigation3.ui.NavDisplay
NavDisplay(
    entryProvider = entryProvider {
        featureAEntryBuilder()
    },
    // ...
)

Usa la inyección de dependencias para agregar entradas a la app principal

En el ejemplo de código anterior, la app principal llama directamente a cada generador de entradas con el DSL entryProvider. Si tu app tiene muchas pantallas o módulos de funciones, es posible que este patrón no se ajuste bien.

Para resolver este problema, haz que cada módulo de funciones aporte sus generadores de entradas a la actividad de la app con la inyección de dependencias.

Por ejemplo, el siguiente código usa multibindings de Dagger, específicamente @IntoSet, para inyectar los generadores de entradas en un Set que pertenece a MainActivity. Luego, se llaman de forma iterativa dentro de entryProvider, lo que elimina la necesidad de llamar explícitamente a numerosas funciones de generador de entradas.

Módulo de funciones

// import dagger.Module
// import dagger.Provides
// import dagger.hilt.InstallIn
// import dagger.hilt.android.components.ActivityRetainedComponent
// import dagger.multibindings.IntoSet

@Module
@InstallIn(ActivityRetainedComponent::class)
object FeatureAModule {

    @IntoSet
    @Provides
    fun provideFeatureAEntryBuilder() : EntryProviderScope<NavKey>.() -> Unit = {
        featureAEntryBuilder()
    }
}

Módulo de la app

// import android.os.Bundle
// import androidx.activity.ComponentActivity
// import androidx.activity.compose.setContent
// import androidx.navigation3.runtime.EntryProviderScope
// import androidx.navigation3.runtime.NavKey
// import androidx.navigation3.runtime.entryProvider
// import androidx.navigation3.ui.NavDisplay
// import javax.inject.Inject

class MainActivity : ComponentActivity() {

    @Inject
    lateinit var entryBuilders: Set<@JvmSuppressWildcards EntryProviderScope<NavKey>.() -> Unit>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            NavDisplay(
                entryProvider = entryProvider {
                    entryBuilders.forEach { builder -> this.builder() }
                },
                // ...
            )
        }
    }
}

Si tus entradas de navegación necesitan navegar (por ejemplo, contienen elementos de la IU que navegan a pantallas nuevas), inyecta un objeto capaz de modificar el estado de navegación de la app en cada función de generador.

Si tu app admite vínculos directos y está modularizada, cada módulo de funciones debe definir las instancias de DeepLinkMatcher para los destinos que posee.

Para recopilar todos los comparadores de tu app, puedes usar multibindings de inyección de dependencias. Por ejemplo, cada módulo de funciones puede aportar sus comparadores a un multibinding @IntoSet de Dagger:

Módulo de funciones

Luego, en el módulo principal de la app, como en tu MainActivity, puedes inyectar el conjunto de comparadores y usarlos para hacer coincidir las solicitudes entrantes:

Módulo de la app

Para obtener más información sobre cómo definir y controlar vínculos directos, consulta Cómo admitir vínculos directos.

Recursos

Para ver ejemplos de código que muestran cómo modularizar el código de Navigation 3, consulta lo siguiente: