Sobes.tech
Middle+

Wie behandelt man in der MVI-Architektur Ereignisse, die nicht gespeichert werden müssen?

sobes.tech KI

Antwort von AI

Um Events-Handling-Ansatz in MVI, der Einmalereignisse (z.B. Toast anzeigen, Navigation) behandelt, die nicht die View-Neuerstellung überleben oder im Zustand gespeichert werden sollen, umfasst verschiedene Methoden:

  1. Side Effects (SingleLiveEvent / Channels):

    Ein spezieller Mechanismus, um Ereignisse vom ViewModel zur View zu senden. SingleLiveEvent (in älteren Projekten oder Bibliotheken wie androidx.lifecycle:lifecycle-livedata-ktx) oder Channel aus Flow (in modernen Projekten). Sie garantieren, dass das Ereignis nur einmal konsumiert wird.

    // ViewModel mit Flow und 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() {
            // Business-Logik...
            viewModelScope.launch {
                _sideEffect.send(SideEffect.ShowToast("Operation erfolgreich!"))
            }
        }
    }
    
    sealed class SideEffect {
        data class ShowToast(val message: String) : SideEffect()
        object NavigateNext : SideEffect()
    }
    
    // In der View (Fragment/Activity) beobachten wir 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) {
            // Toast anzeigen
        }
    
        private fun navigateToNextScreen() {
            // Navigation
        }
    }
    
  2. Single-Use-Events im State:

    Weniger bevorzugt, aber manchmal in einfachen Fällen verwendet. Es wird ein Flag oder Feld im UiState hinzugefügt, das auf das Vorhandensein eines Einmal-Events hinweist. Nach der Verarbeitung sendet die View einen neuen Intent an das ViewModel, um dieses Flag im Zustand zurückzusetzen. Dies kann die Logik verkomplizieren und bei falscher Handhabung oder View-Rekonstruktion zu wiederholtem Auslösen führen.

    // Beispiel für State mit Einmal-Flag
    data class MyUiState(
        val data: List<Item> = emptyList(),
        val isLoading: Boolean = false,
        val showSuccessToast: Boolean = false // Flag für Einmal-Event
    )
    
    // Im ViewModel nach erfolgreicher Operation
    fun processSuccess() {
        _uiState.update { it.copy(showSuccessToast = true) }
    }
    
    // In der View bei Verarbeitung von showSuccessToast = true
    if (state.showSuccessToast) {
        showToast("Erfolg!")
        // Wichtig: Intent zum Zurücksetzen des Flags senden
        viewModel.onToastShown()
    }
    
    // Im ViewModel zum Zurücksetzen des Flags
    fun onToastShown() {
        _uiState.update { it.copy(showSuccessToast = false) }
    }
    

Der am besten empfohlene Ansatz in modernem Android mit Flow und MVI ist die Verwendung von Side Effects über Channel. Dies ist eine saubere, sichere und vorhersehbare Lösung für Einmal-Events.