DEV Community

Cover image for Navegação Descomplicada no Android utilizando MVVM-C com NavigationManager
Gustavo Alencar
Gustavo Alencar

Posted on

Navegação Descomplicada no Android utilizando MVVM-C com NavigationManager

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:

Estrutura MVVM-C

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)
  }
}
Enter fullscreen mode Exit fullscreen mode

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

  {...}
}
Enter fullscreen mode Exit fullscreen mode

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(...)
  }
}
Enter fullscreen mode Exit fullscreen mode

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)