Percorremos os módulos: Navegação em um aplicativo de vários módulos com o Jetpack


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

Source: https://habr.com/ru/post/pt475104/


All Articles