Introdução ao padrão MVI no desenvolvimento Android
Afinal o que é o padrão MVI?
MVI é um dos mais novos padrões de arquitetura para Android, inspirado na natureza unidirecional e cíclica da estrutura Cycle.js.
O MVI funciona de maneira muito diferente em relação aos padrões MVC, MVP ou MVVM.
Basicamente consiste em três principais componentes:
Model : representa o estado. É representado por uma classe imutável para garantir que o fluxo de dados seja unidirecional e que o estado não seja alterado em nenhum outro lugar no aplicativo, se tornando a única fonte de verdade [single source of truth]
View : representa a camada de interface com o usuário. Entrega as ações do usuário e atualiza a interface refletindo as mudanças de estados recebidas.
Intent : são as ações desencadeados pelo usuário. [Não confunda com o Android Intent.] São capturadas por meio da interação do usuário com a View.
Entendendo o Fluxo de dados no MVI
O
1. A View é renderizada com base no estado inicial da classe UiState
2. Quando uma interação do usuário ocorre, a View envia essa ação para a viewModel.
3. A ação recebida gera um evento que é processado e seu resultado deste processamento emite um novo estado.
4. O novo estado é imediatamente refletido na View.
Representando Estados
data class LoginUiStates(
val isSuccessLogin : Boolean = false,
val allFieldsAreFilled : Boolean = false,
val name:String = "",
val pass:String = "",
val isNameError : Boolean = false,
val nameErrorHint : String = "Digite seu nome",
val isPassError : Boolean = false,
val passErrorHint : String = "A senha deve conter 4 dígitos"
)
Representando Eventos
sealed class LoginEvent {
data class ValidateNameField(val name: String) : LoginEvent()
data class ValidatePassField(val pass: String) : LoginEvent()
object ValidateLogin : LoginEvent()
}
Exemplo de uma implementação na viewModel usando flow:
private val _uiState: MutableStateFlow<LoginUiStates> =
MutableStateFlow(LoginUiStates.Empty)
var uiSTate: StateFlow<LoginUiStates> = _uiState
private val actions = MutableSharedFlow<LoginEvent>()
A cada evento recebido nossa View invoca o método onEvent da viewModel :
fun onEvent(event: LoginEvent) {
viewModelScope.launch {
actions.emit(event)
}
}
A cada evento emitido nossa viewModel desencadeia uma ação, e a partir do resultado atualizamos o estado:
private fun handleEvents() {
viewModelScope.launch {
actions.collect { event ->
when (event) {
is LoginEvent.ValidateLogin -> validatingLogin()
is LoginEvent.ValidateNameField -> validateNameField(event)
is LoginEvent.ValidatePassField -> validatePassField(event)
}
}
}
}
Ação desencadeada por um evento
private fun validatePassField(event: LoginEvent.ValidatePassField) {
_uiState.update { it.copy(pass = event.pass) }
if (event.pass.isNotEmpty() && event.pass.length > 4)
_uiState.update { it.copy(isPassError = false) }
else _uiState.update { it.copy(isPassError = true) }
}
Benefícios do MVI com Jetpack Compose
1. Separação de responsabilidades: O MVI divide as responsabilidades entre modelo, view e viewModel, tornando o código mais modular e fácil de entender.
2. Testabilidade aprimorada: Como o estado é imutável e a lógica de negócio é isolada na ViewModel, os testes se tornam mais simples e confiáveis.
3. Previsibilidade do fluxo de dados: O fluxo unidirecional do MVI garante que o estado seja atualizado de maneira consistente, tornando mais fácil rastrear o fluxo de dados no aplicativo.
4. Reprodutibilidade do estado: Com o MVI, o estado do aplicativo é reprodutível, o que significa que é possível reproduzir determinados estados para depuração e correção de bugs e testes de UI.
Conclusão
O padrão MVI oferece uma ótima opção de padrão arquitetural quando utilizamos o Jetpack Compose. Ele ajuda a separar as responsabilidades, tornar o código mais testável e oferecer um fluxo de dados previsível.
Link : https://github.com/JoaoSantosBH/MoviesCompose
Referencias:
https://medium.com/swlh/mvi-architecture-with-android-fcde123e3c4a
https://www.kodeco.com/817602-mvi-architecture-for-android-tutorial-getting-started