
Quase todo projeto em crescimento, mais cedo ou mais tarde, começa a olhar para a arquitetura multimodular. Os desenvolvedores não querem esperar até que todo o projeto seja remontado, quando apenas um recurso foi alterado. A multimodularidade ajuda a isolar os recursos do aplicativo, reduzindo assim o tempo de criação. Mas esse isolamento impõe algumas restrições no escopo dos componentes. Quando usamos a navegação do Jetpack em um projeto com um módulo, o gráfico de navegação está disponível em qualquer pacote de aplicativos, sempre podemos especificar explicitamente qual ação o NavController deve executar e também ter acesso ao host global se houver fragmentos aninhados no projeto. Porém, quando existem muitos módulos, surgem questões: onde construir o gráfico de navegação, como acessá-lo e como não se confundir nas dependências dos módulos. Falaremos sobre tudo isso sob o corte.
Gráfico de navegação
A coisa mais importante a ser lembrada ao projetar um aplicativo multi-módulo são as dependências. As dependências na árvore de dependências do módulo devem ser direcionadas em uma direção.

O módulo mais dependente em um aplicativo multi-módulo é sempre o módulo de aplicativo. Ele conhece quase todos os outros módulos. No aplicativo, as DIs geralmente são implementadas usando várias estruturas. Usando essa dependência do módulo de aplicativo, você pode implementar o gráfico de navegação do host principal nele.
Você deve sempre lembrar que o módulo do aplicativo deve implementar o mínimo de funcionalidade possível, pois é o mais dependente e quase todas as alterações no projeto resultarão na remontagem do módulo do aplicativo.
Falar é barato. Mostre-me o código
E imediatamente para um exemplo real. Nosso caso: o ponto de entrada para o aplicativo é a tela inicial, que determina em qual tela continuar: a principal funcionalidade ou autorização. Na tela de autorização, há uma transição apenas para a funcionalidade principal. Como sempre, construímos um gráfico de navegação - nada complicado.

Acesso à navegação dentro do módulo
Quando chega a hora de fazer a transição de uma tela para outra em outro módulo, surge a pergunta - como?
De fato, dentro do módulo de recursos, não há acesso ao gráfico de navegação para obter o ID da ação que o NavController deve executar.
Isso é resolvido implementando o DI usando interfaces. Em vez do módulo de recurso, dependendo do gráfico global de navegação do módulo de aplicativo, criaremos uma interface e chamaremos de WhatToNavCommandProvider, cujas variáveis são comandos de navegação.
SplashNavCommandProvider.kt
interface SplashNavCommandProvider { val toAuth: NavCommand val toMain: NavCommand }
A própria interface do provedor de comandos será implementada no módulo de aplicativo e a classe de comando de navegação terá os mesmos campos que os argumentos para o método NavController.navigate
NavCommand.kt
data class NavCommand( val action: Int, var args: Bundle? = null, val navOptions: NavOptions? = null )
Vamos ver como fica na prática. Na tela inicial, são possíveis 2 transições: para a tela de autorização e para a tela funcional principal. No módulo inicial, crie uma interface:
SplashNavCommandProvider.kt
interface SplashNavCommandProvider { val toAuth: NavCommand val toMain: NavCommand }
No módulo de aplicativo, criamos uma implementação dessa interface e, usando o di framework (eu tenho o Dagger), fornecemos isso através da interface inicial do módulo.
SplashNavCommandProviderImpl.kt - implementação do 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 fornecer dependências
@Module interface SplashNavigationModule { @Binds fun bindSplashNavigator(impl: SplashNavCommandProviderImpl): SplashNavCommandProvider }
AppActivityModule.kt - o principal módulo DI do aplicativo
@Module interface AppActivityModule { @FragmentScope @ContributesAndroidInjector( modules = [ SplashNavigationModule::class ] ) fun splashFragmentInjector(): SplashFragment … }
No módulo inicial, implementamos a implementação no MV (aqui), Presenter ou ViewModel ...
SplashViewModel.kt
class SplashViewModel @Inject constructor( private val splashNavCommandProvider: SplashNavCommandProvider ) ...
Quando a lógica da tela considera que é hora de mudar para outra tela, transferimos um comando para o nosso fragmento e informamos que precisamos mudar para outra tela.
Seria possível implementar a implementação do SplashNavCommandProvider diretamente no fragmento, mas perdemos a capacidade de testar a navegação.
No próprio fragmento, para concluir a transição, você precisa obter o NavController. Se a tela atual não é um fragmento aninhado, simplesmente obtemos o NavController com o método findNavController () e chamamos o método de navegação:
findNavController().navigate(toMain)
Você pode torná-lo um pouco mais conveniente escrevendo uma extensão para o fragmento
Framentmentxt.kt
fun Fragment.navigate(navCommand: NavCommand) { findNavController().navigate(navCommand.action, navCommand.args, navCommand.navOptions) }
Por que apenas para o fragmento? Como eu uso a abordagem SingleActivity, se você tiver várias delas, poderá criar extensões também para Activity.
Então a navegação dentro do fragmento ficará assim
navigate(toMain)
Fragmentos aninhados
A navegação em fragmentos aninhados pode ser de dois tipos:
- Transição em um contêiner aninhado
- Indo no recipiente um ou mais níveis mais altos. Por exemplo, um host de atividade global
No primeiro caso, tudo é simples, a extensão que escrevemos acima é adequada para nós. E para concluir a transição no segundo caso, você precisa obter o NavController do host desejado. Para fazer isso, você precisa obter o ID desse host dentro do módulo. Como apenas o módulo que implementa o gráfico de navegação desse host tem acesso a ele, criaremos uma dependência e os implementaremos nos módulos de recursos, nos quais o acesso a um NavController específico é necessário, através do Dagger.
GlobalHostModule.kt - módulo DI para fornecer dependências globais de ID de host
@Provides @GlobalHost fun provideGlobalHostId(): Int = R.id.host_global
AppActivityModule.kt - o principal módulo DI do aplicativo
@FragmentScope @ContributesAndroidInjector( modules = [ GlobalHostModule::class, ProfileNavigationModule::class, ... ] ) fun profileKnownFragmentInjector(): ProfileKnownFragment
Incorporando a dependência da ID do host no fragmento
@Inject @GlobalHost var hostId = 0
Quando fragmentos são aninhados, vale a pena criar um Qualificador para cada host ou usar o Qualificador existente nomeado para que Dagger entenda qual pretende fornecer.
Globalhost.kt
@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class GlobalHost
Depois que o ID de dependência do host desejado for obtido no fragmento, você poderá obter o NavController pelo ID do host. Melhoraremos nossa extensão para a capacidade de fazer transições em qualquer contêiner:
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 no fragmento
navigate(toAuth, hostId)
Esses foram os destaques da navegação usando o Jetpack em uma arquitetura de vários módulos. Se você tiver alguma dúvida, terei prazer em respondê-las nos comentários :)