Sobes.tech
Middle+

Hoe om te gaan met gebeurtenissen die niet opgeslagen hoeven te worden in de MVI-architectuur?

sobes.tech AI

Antwoord van AI

Om unieke gebeurtenissen (bijvoorbeeld het tonen van een toast, navigatie) in MVI te verwerken, die niet de View of de staat moeten overleven of in de staat moeten worden opgeslagen, worden verschillende benaderingen gebruikt:

  1. Side Effects (SingleLiveEvent / Channels):

    Een speciale mechanisme om gebeurtenissen van ViewModel naar View te sturen. SingleLiveEvent (in oude projecten of bibliotheken zoals androidx.lifecycle:lifecycle-livedata-ktx), of Channel uit Flow (in moderne projecten). Ze garanderen dat het evenement slechts één keer wordt geconsumeerd.

    // ViewModel met Flow en 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() {
            // Businesslogica...
            viewModelScope.launch {
                _sideEffect.send(SideEffect.ShowToast("Operatie succesvol!"))
            }
        }
    }
    
    sealed class SideEffect {
        data class ShowToast(val message: String) : SideEffect()
        object NavigateNext : SideEffect()
    }
    
    // In de View (Fragment/Activity) observeren we 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) {
            // Implementatie voor toast
        }
    
        private fun navigateToNextScreen() {
            // Navigatie
        }
    }
    
  2. Eenmalige gebeurtenissen in de staat:

    Minder aanbevolen, maar soms wordt het gebruikt in eenvoudige gevallen. Voeg een vlag of veld toe aan de UiState die aangeeft dat er een eenmalig evenement is. Na verwerking stuurt de View een nieuwe Intent naar de ViewModel om deze vlag of veld in de staat te resetten. Dit kan de logica compliceren en leiden tot herhaalde triggers bij verkeerd beheer of hercreatie van de View.

    // Voorbeeld van State met een eenmalige vlag
    data class MyUiState(
        val data: List<Item> = emptyList(),
        val isLoading: Boolean = false,
        val showSuccessToast: Boolean = false // Vlag voor eenmalig evenement
    )
    
    // In de ViewModel na een succesvolle operatie
    fun processSuccess() {
        _uiState.update { it.copy(showSuccessToast = true) }
    }
    
    // In de View bij het verwerken van showSuccessToast = true
    if (state.showSuccessToast) {
        showToast("Succes!")
        // Belangrijk: stuur een Intent om de vlag te resetten
        viewModel.onToastShown()
    }
    
    // In de ViewModel om de vlag te resetten
    fun onToastShown() {
        _uiState.update { it.copy(showSuccessToast = false) }
    }
    

De meest aanbevolen aanpak in moderne Android met Flow en MVI is het gebruik van Side Effects via Channel. Dit is een schone, veilige en voorspelbare oplossing voor eenmalige gebeurtenissen.