Uma abordagem prática para organizar a lógica de navegação usando Coordinator (MVVM-C) no desenvolvimento nativo do Android
Introdução
Se toda experiência não é única e você já se sentiu perdido entre Activities
, Fragments
, Intents
, Navigation Graphs
e Args/SafeArgs
no Android, não estamos sós.
A navegação em projetos Android Nativo pode rapidamente se tornar um caos, principalmente à medida a aplicação escala. Após anos trabalhando com desenvolvimento iOS, busquei aplicar conceitos semelhantes no Android.
Neste artigo, apresento uma proposta baseada na arquitetura MVVM-C
(Model-View-ViewModel-Coordinator), utilizando um componente chamado NavigationManager
para tornar a navegação mais organizada, escalável e testável.
Sumário
- O que é MVVM-C?
- O papel do NavigationManager
- Integração com o Coordinator
- Inicialização do fluxo
- Conclusão
O que é MVVM-C?
MVVM-C é uma extensão do padrão MVVM, onde a responsabilidade de navegação é delegada a um novo componente: o Coordinator. O objetivo é retirar a lógica de navegação da camada de View e centralizá-la em um controlador especializado, que decide quais telas devem ser mostradas e em qual sequência.
Sendo assim, temos:
No desenvolvimento iOS, esse padrão é amplamente usado, pois o NavigationController do UIKit favorece essa separação. A proposta aqui é aplicar essa mesma ideia no Android Nativo.
O papel do NavigationManager
O NavigationManager
é um controlador simples que abstrai a navegação utilizando o FragmentManager
. Ele depende de uma Activity
base que contenha um Container, que atuará como o stack visual da navegação.
Dessa forma, temos então um exemplo simples:
// Exemplo simples de uma implementação para o NavigationManager
class NavigationManager(
// Activity onde será controlada a navegação
private val navigationActivity: AppCompatActivity,
// Fragment container onde será "plotado" o stack de telas/fragments
private val containerId: Int
) {
private val fragmentManager = navigationActivity.supportFragmentManager
fun to(fragment: Fragment) {
fragmentManager.commit {
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_MATCH_ACTIVITY_OPEN)
add(containerId, fragment)
addToBackStack(fragment.javaClass.simpleName)
}
}
fun back() {
fragmentManager.popBackStack()
}
fun toRoot() {
val backStackCount = fragmentManager.backStackEntryCount
if (backStackCount == 0) return
val firstEntry = fragmentManager.getBackStackEntryAt(0)
fragmentManager.popBackStack(firstEntry.id, FragmentManager.POP_BACK_STACK_INCLUSIVE)
}
}
Integração com o Coordinator
Com o NavigationManager, podemos agora implementar um Coordinator para orquestrar o fluxo da aplicação.
Podemos, então, pensar nossa arquitetura da seguinte forma:
Nesta imagem, é possível observar que podemos fazer uma completa separação entre as features, onde o NavigationManager
é o elo que liga o fluxo, sendo esta controlada pelo Coordinator
. Este fica responsável, então, pela inicialização de cada fluxo e seus redirecionamentos futuros.
Agora, podemos ver a nível de código um exemplo desse funcionamento do Coordinator
:
class ExampleCoordinator(
private val navigationManager: NavigationManager
) {
// método de inicialização deste fluxo. Podendo ser chamado,
// direta ou indiretamente, por outros coordinators
fun start(parameters: Parameters, {...}) {
val viewModel = ExampleViewModel(
coordinator = this,
parameters = parameters,
{...}
)
val fragment: ExampleFragment(viewModel)
navigationManager.to(fragment)
}
{...}
}
Inicialização do fluxo
Por fim, mas não menos importante, para iniciar um fluxo de navegação a partir da nossa Activity
, basta se definir o container e teu ID, para então chamar o coordinator inicial, como pode ser visto no exemplo abaixo:
class MainActivity: AppCompatActivity {
override fun onCreate(savedInstanceState: Bundle?) {
setContent {
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
FragmentContainerView(context).apply {
id = R.id.fragment_container
}
}
)
}
{...}
val navigationManager = NavigationManager(
navigationActivity = this,
containerId = R.id.fragment_container
)
ExampleCoordinator(navigationManager).start(...)
}
}
A MainActivity
funciona como ponto de entrada. No exemplo acima, usamos setContent
e AndroidView
para criar o FragmentContainerView
dinamicamente, mas você pode definir esse container também via XML.
Conclusão
Adotar MVVM-C no Android com um NavigationManager simplifica a navegação e organiza melhor a estrutura das features. Separar a lógica de navegação da UI facilita a modularização, favorece injeção de dependências e torna os fluxos testáveis com testes unitários, evitando dependência de testes de UI, que são mais custosos.
Além disso, pode ser facilmente migrado para projetos já existentes, em vista que cada feature pode ter uma Activity de referência para fazer a inicialização do fluxo
Top comments (0)