
Casi todos los proyectos en crecimiento, tarde o temprano, comienzan a mirar hacia la arquitectura multimodular. Los desarrolladores no quieren esperar hasta que todo el proyecto se vuelva a ensamblar, cuando solo se haya cambiado una característica. La multimodularidad ayuda a aislar las características de la aplicación entre sí, lo que reduce el tiempo de construcción. Pero este aislamiento impone algunas restricciones en el alcance de los componentes. Cuando utilizamos la navegación desde Jetpack en un proyecto con un módulo, el gráfico de navegación está disponible desde cualquier paquete de aplicaciones, siempre podemos especificar explícitamente qué acción debe realizar NavController, y también tener acceso al host global si hay fragmentos anidados en el proyecto. Pero cuando hay muchos módulos, surgen preguntas: dónde construir el gráfico de navegación, cómo acceder a él y cómo no confundirse en las dependencias de los módulos. Hablaremos de todo esto debajo del corte.
Gráfico de navegación
Lo más importante para recordar al diseñar una aplicación de varios módulos son las dependencias. Las dependencias en el árbol de dependencias del módulo deben dirigirse en una dirección.

El módulo más dependiente en una aplicación de múltiples módulos es siempre el módulo de la aplicación. Él conoce casi todos los otros módulos. En la aplicación, los DI generalmente se implementan utilizando varios marcos. Con esta dependencia del módulo de la aplicación, puede implementar el gráfico de navegación del host principal en él.
Siempre debe recordar que el módulo de la aplicación debe implementar la menor funcionalidad posible, ya que es el más dependiente y casi cualquier cambio en el proyecto dará como resultado el reensamblaje del módulo de la aplicación.
Hablar es barato. Muéstrame el código
E inmediatamente a un ejemplo real. Nuestro caso: el punto de entrada a la aplicación es la pantalla de inicio, determina a qué pantalla ir: la funcionalidad principal o la autorización. Desde la pantalla de autorización, solo hay una transición a la funcionalidad principal. Como de costumbre, construimos un gráfico de navegación, nada complicado.

Acceso a la navegación dentro del módulo.
Cuando llega el momento de hacer una transición de una pantalla a una pantalla en otro módulo, surge la pregunta: ¿cómo?
De hecho, dentro del módulo de funciones no hay acceso al gráfico de navegación para obtener la identificación de acción que debe ejecutar NavController.
Esto se resuelve implementando DI utilizando interfaces. En lugar del módulo de función que depende del gráfico global de navegación desde el módulo de la aplicación, crearemos una interfaz y la llamaremos WhatToNavCommandProvider, cuyas variables son comandos de navegación.
SplashNavCommandProvider.kt
interface SplashNavCommandProvider { val toAuth: NavCommand val toMain: NavCommand }
La interfaz del proveedor de comandos se implementará en el módulo de la aplicación, y la clase del comando de navegación tendrá los mismos campos que los argumentos para el método NavController.navigate
NavCommand.kt
data class NavCommand( val action: Int, var args: Bundle? = null, val navOptions: NavOptions? = null )
Veamos cómo se ve en la práctica. Desde la pantalla de bienvenida, son posibles 2 transiciones: a la pantalla de autorización y a la pantalla funcional principal. En el módulo de bienvenida, cree una interfaz:
SplashNavCommandProvider.kt
interface SplashNavCommandProvider { val toAuth: NavCommand val toMain: NavCommand }
En el módulo de la aplicación, creamos una implementación de esta interfaz y, utilizando el marco de trabajo di (tengo Dagger), la proporcionamos a través de la interfaz de bienvenida al módulo.
SplashNavCommandProviderImpl.kt - implementación de CommandProvider
class SplashNavCommandProviderImpl @Inject constructor() : SplashNavCommandProvider { override val toAuth: NavCommand = NavCommand(R.id.action_splashFragment_to_authFragment) override val toMain: NavCommand = NavCommand(R.id.action_splashFragment_to_mainFragment) }
SplashNavigationModule.kt - Módulo DI para proporcionar dependencias
@Module interface SplashNavigationModule { @Binds fun bindSplashNavigator(impl: SplashNavCommandProviderImpl): SplashNavCommandProvider }
AppActivityModule.kt: el módulo DI principal de la aplicación
@Module interface AppActivityModule { @FragmentScope @ContributesAndroidInjector( modules = [ SplashNavigationModule::class ] ) fun splashFragmentInjector(): SplashFragment … }
En el módulo de bienvenida, implementamos la implementación en MV (aquí) ya sea Presenter o ViewModel ...
SplashViewModel.kt
class SplashViewModel @Inject constructor( private val splashNavCommandProvider: SplashNavCommandProvider ) ...
Cuando la lógica de la pantalla considera que es hora de cambiar a otra pantalla, transferimos un comando a nuestro fragmento e informamos que necesitamos cambiar a otra pantalla.
Sería posible implementar la implementación de SplashNavCommandProvider directamente en el fragmento, pero luego perdemos la capacidad de probar la navegación.
En el fragmento mismo, para completar la transición, necesita obtener NavController. Si la pantalla actual no es un fragmento anidado, simplemente obtenemos el NavController con el método findNavController () y llamamos al método de navegación:
findNavController().navigate(toMain)
Puede hacerlo un poco más conveniente escribiendo una extensión para el fragmento
Framentmentxt.kt
fun Fragment.navigate(navCommand: NavCommand) { findNavController().navigate(navCommand.action, navCommand.args, navCommand.navOptions) }
¿Por qué solo por el fragmento? Como utilizo el enfoque SingleActivity, si tiene varios de ellos, puede crear extensiones también para Activity.
Entonces la navegación dentro del fragmento se verá así
navigate(toMain)
Fragmentos Anidados
La navegación en fragmentos anidados puede ser de dos tipos:
- Transición en un contenedor anidado
- Entrando en el contenedor uno o más niveles más alto. Por ejemplo, un host de actividad global
En el primer caso, todo es simple, la extensión que escribimos arriba es adecuada para nosotros. Y para completar la transición en el segundo caso, debe obtener el NavController del host deseado. Para hacer esto, debe obtener la identificación de este host dentro del módulo. Dado que solo el módulo que implementa el gráfico de navegación de este host tiene acceso a él, crearemos una dependencia e implementaremos en los módulos de características, donde se necesita acceso a un NavController específico, a través de Dagger.
GlobalHostModule.kt - Módulo DI para proporcionar dependencias de identificación de host global
@Provides @GlobalHost fun provideGlobalHostId(): Int = R.id.host_global
AppActivityModule.kt: el módulo DI principal de la aplicación
@FragmentScope @ContributesAndroidInjector( modules = [ GlobalHostModule::class, ProfileNavigationModule::class, ... ] ) fun profileKnownFragmentInjector(): ProfileKnownFragment
Incrustar la dependencia del ID del host en un fragmento
@Inject @GlobalHost var hostId = 0
Cuando los fragmentos están anidados, vale la pena crear un calificador para cada host o usar el calificador existente nombrado para que Dagger comprenda qué int proporcionar.
Globalhost.kt
@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class GlobalHost
Después de obtener el ID de dependencia del host deseado en el fragmento, puede obtener el NavController mediante el ID del host. Mejoraremos nuestra extensión para la capacidad de realizar transiciones en cualquier contenedor:
Framentmentxt.kt
fun Fragment.navigate(navCommand: NavCommand, hostId: Int? = null) { val navController = if (hostId == null) { findNavController() } else { Navigation.findNavController(requireActivity(), hostId) } navController.navigate(navCommand.action, navCommand.args, navCommand.navOptions) }
Código en fragmento
navigate(toAuth, hostId)
Estos fueron los aspectos más destacados de navegar usando Jetpack en una arquitectura de módulos múltiples. Si tiene alguna pregunta, me complacerá responderla en los comentarios :)