¿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:
-
Efectos secundarios (SingleLiveEvent / Channels):
Mecanismo especial para enviar eventos desde ViewModel a View.
SingleLiveEvent(en proyectos antiguos o bibliotecas comoandroidx.lifecycle:lifecycle-livedata-ktx), oChannelde 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 } } -
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
UiStateque indica la presencia de un evento de un solo uso. Después de procesar el evento en la vista, la vista envía un nuevoIntental 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.