Sobes.tech
Middle+

¿Cómo manejar en la arquitectura MVI los eventos que no necesitan ser guardados?

sobes.tech AI

Respuesta de la IA

Para manejar eventos únicos (por ejemplo, mostrar una tostada, navegación) en MVI, que no deben sobrevivir a la recreación de la vista ni almacenarse en el estado, se utilizan diferentes enfoques:

  1. Efectos secundarios (SingleLiveEvent / Channels):

    Mecanismo especial para enviar eventos desde ViewModel a View. SingleLiveEvent (en proyectos antiguos o bibliotecas como androidx.lifecycle:lifecycle-livedata-ktx), o Channel de Flow (en proyectos modernos). Garantizan que el evento se consuma solo una vez.

    // ViewModel con Flow y 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 negocio...
            viewModelScope.launch {
                _sideEffect.send(SideEffect.ShowToast("¡Operación exitosa!"))
            }
        }
    }
    
    sealed class SideEffect {
        data class ShowToast(val message: String) : SideEffect()
        object NavigateNext : SideEffect()
    }
    
    // En la vista (Fragmento/Actividad), observamos 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) {
            // Implementación para mostrar la tostada
        }
    
        private fun navigateToNextScreen() {
            // Implementación para navegar
        }
    }
    
  2. Eventos de uso único en el estado:

    Es una opción menos preferida, pero a veces se usa en casos simples. Se añade una bandera o campo en el propio estado UiState que indica la presencia de un evento de un solo uso. Después de procesar el evento en la vista, la vista envía un nuevo Intent al ViewModel para restablecer esa bandera o campo en el estado. Esto puede complicar la lógica y potencialmente causar que se vuelva a activar en caso de manejo incorrecto o recreación de la vista.

    // Ejemplo de estado con bandera de un solo uso
    data class MyUiState(
        val data: List<Item> = emptyList(),
        val isLoading: Boolean = false,
        val showSuccessToast: Boolean = false // Bandera para evento de un solo uso
    )
    
    // En ViewModel, tras una operación exitosa
    fun processSuccess() {
        _uiState.update { it.copy(showSuccessToast = true) }
    }
    
    // En la vista, al procesar showSuccessToast = true
    if (state.showSuccessToast) {
        showToast("¡Éxito!")
        // Importante: enviar un Intent para restablecer la bandera
        viewModel.onToastShown()
    }
    
    // En ViewModel, para restablecer la bandera
    fun onToastShown() {
        _uiState.update { it.copy(showSuccessToast = false) }
    }
    

El enfoque más recomendado en Android moderno con Flow y MVI es usar Efectos secundarios a través de Channel. Es una solución limpia, segura y predecible para eventos de un solo uso.