Sobes.tech
Middle+

Como lidar na arquitetura MVI com eventos que não precisam ser salvos?

sobes.tech IA

Resposta da IA

Para lidar com eventos únicos (por exemplo, mostrar uma toast, navegação) no MVI, que não devem sobreviver à recriação da View nem serem armazenados no estado, utilizam-se diferentes abordagens:

  1. Efeitos Colaterais (SingleLiveEvent / Channels):

    Mecanismo especial para enviar eventos do ViewModel para a View. SingleLiveEvent (em projetos antigos ou bibliotecas como androidx.lifecycle:lifecycle-livedata-ktx), ou Channel do Flow (em projetos modernos). Garantem que o evento seja consumido apenas uma vez.

    // ViewModel com Flow e Channel
    import kotlinx.coroutines.channels.Channel
    import kotlinx.coroutines.flow.receiveAsFlow
    import androidx.lifecycle.ViewModel
    import androidx.lifecycle.viewModelScope
    import kotlinx.coroutines.launch
    
    class MyViewModel : ViewModel() {
    
        private val _sideEffect = Channel<SideEffect>(Channel.BUFFERED)
        val sideEffect = _sideEffect.receiveAsFlow()
    
        fun doSomething() {
            // Lógica de negócio...
            viewModelScope.launch {
                _sideEffect.send(SideEffect.ShowToast("Operação bem-sucedida!"))
            }
        }
    }
    
    sealed class SideEffect {
        data class ShowToast(val message: String) : SideEffect()
        object NavigateNext : SideEffect()
    }
    
    // Na View (Fragmento/Atividade), observando SideEffect
    import androidx.fragment.app.Fragment
    import androidx.lifecycle.lifecycleScope
    import kotlinx.coroutines.flow.collect
    import kotlinx.coroutines.launch
    
    class MyFragment : Fragment() {
    
        private val viewModel: MyViewModel by viewModels()
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
    
            viewLifecycleOwner.lifecycleScope.launch {
                viewModel.sideEffect.collect { effect ->
                    when (effect) {
                        is SideEffect.ShowToast -> showToast(effect.message)
                        SideEffect.NavigateNext -> navigateToNextScreen()
                    }
                }
            }
        }
    
        private fun showToast(message: String) {
            // Implementação para mostrar toast
        }
    
        private fun navigateToNextScreen() {
            // Implementação para navegação
        }
    }
    
  2. Eventos de uso único no Estado:

    Menos recomendado, mas às vezes usado em casos simples. Adiciona-se uma flag ou campo no próprio estado UiState que indica a presença de um evento de uso único. Após o processamento do evento na View, a View envia um novo Intent ao ViewModel para resetar essa flag ou campo no estado. Isso pode complicar a lógica e potencialmente levar à reativação em caso de má gestão ou recriação da View.

    // Exemplo de Estado com flag de uso único
    data class MyUiState(
        val data: List<Item> = emptyList(),
        val isLoading: Boolean = false,
        val showSuccessToast: Boolean = false // Flag para evento de uso único
    )
    
    // No ViewModel, após operação bem-sucedida
    fun processSuccess() {
        _uiState.update { it.copy(showSuccessToast = true) }
    }
    
    // Na View, ao processar showSuccessToast = true
    if (state.showSuccessToast) {
        showToast("Sucesso!")
        // Importante: enviar um Intent para resetar a flag
        viewModel.onToastShown()
    }
    
    // No ViewModel, para resetar a flag
    fun onToastShown() {
        _uiState.update { it.copy(showSuccessToast = false) }
    }
    

A abordagem mais recomendada no Android moderno com Flow e MVI é usar Efeitos Colaterais via Channel. É uma solução limpa, segura e previsível para eventos de uso único.