Sobes.tech
Middle+

Hogyan kezeljük az MVI architektúrában azokat az eseményeket, amelyeket nem kell menteni?

sobes.tech MI

Válasz az MI-től

Az MVI-ben az egyszeri események (például toast megjelenítése, navigáció) kezelésére, amelyek nem szabad, hogy túléljék a nézet újraalkotását vagy állapotban való tárolást, különböző megközelítéseket alkalmaznak:

  1. Side Effects (SingleLiveEvent / Channels):

    Egy speciális mechanizmus az események küldésére a ViewModel-ből a View-ba. SingleLiveEvent (régebbi projektekben vagy könyvtárakban, mint az androidx.lifecycle:lifecycle-livedata-ktx), vagy Channel a Flow-ból (modern projektekben). Ezek garantálják, hogy az eseményt csak egyszer fogyasztják el.

    // Flow és Channel használatával a ViewModel-ben
    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() {
            // Üzleti logika...
            viewModelScope.launch {
                _sideEffect.send(SideEffect.ShowToast("Sikeresen végrehajtva!"))
            }
        }
    }
    
    sealed class SideEffect {
        data class ShowToast(val message: String) : SideEffect()
        object NavigateNext : SideEffect()
    }
    
    // A View-ban (Fragment/Activity) figyelünk a SideEffect-re
    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 megjelenítésének implementálása
        }
    
        private fun navigateToNextScreen() {
            // Navigáció implementálása
        }
    }
    
  2. Egyszeri események állapotban:

    Kevésbé ajánlott megközelítés, de néha egyszerű esetekben használják. Egy zászlót vagy mezőt adunk hozzá az UiState-hez, ami jelzi az egyszeri esemény meglétét. Az esemény feldolgozása után a View küld egy új Intent-et a ViewModel-nek, hogy resetelje ezt a zászlót/mezőt az állapotban. Ez bonyolíthatja a logikát, és ismételt eseményt eredményezhet rossz kezelés vagy újraalkotás esetén.

    // Egyszeri zászlóval ellátott állapot példa
    data class MyUiState(
        val data: List<Item> = emptyList(),
        val isLoading: Boolean = false,
        val showSuccessToast: Boolean = false // Zászló az egyszeri eseményhez
    )
    
    // A ViewModel-ben sikeres műveletnél
    fun processSuccess() {
        _uiState.update { it.copy(showSuccessToast = true) }
    }
    
    // A View-ban, amikor a showSuccessToast = true
    if (state.showSuccessToast) {
        showToast("Siker!")
        // Fontos: küldjön Intent-et a zászló reseteléséhez
        viewModel.onToastShown()
    }
    
    // A ViewModel-ben a zászló reseteléséhez
    fun onToastShown() {
        _uiState.update { it.copy(showSuccessToast = false) }
    }
    

A modern Android fejlesztésben Flow és MVI esetén a legjobb megközelítés a Channel-en keresztül történő Side Effects használata. Ez tiszta, biztonságos és előre jelezhető megoldás az egyszeri események kezelésére.