Sobes.tech
Middle+

Kaip MVI architektūroje tvarkyti įvykius, kurių nereikia išsaugoti?

sobes.tech AI

Atsakymas iš AI

Vienkartinių įvykių (pavyzdžiui, tostų rodymas, navigacija), kurie neturėtų išlikti per View perkūrimą ar būti išsaugoti būsenoje, apdorojimui MVI naudojami įvairūs požiūriai:

  1. Side Effects (SingleLiveEvent / Channels):

    Specialus mechanizmas, skirtas siųsti įvykius iš ViewModel į View: SingleLiveEvent (senesniuose projektuose arba androidx.lifecycle:lifecycle-livedata-ktx bibliotekose), arba Channel iš Flow. Jie garantuoja, kad įvykis bus suvartotas tik vieną kartą:

    // ViewModel su Flow ir 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() {
            // Verslo logika...
            viewModelScope.launch {
                _sideEffect.send(SideEffect.ShowToast("Veiksmas sėkmingai atliktas!"))
            }
        }
    }
    
    sealed class SideEffect {
        data class ShowToast(val message: String) : SideEffect()
        object NavigateNext : SideEffect()
    }
    
    // Vaizdas (Fragment/Activity) stebi 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) {
            // Tosto rodymo įgyvendinimas
        }
    
        private fun navigateToNextScreen() {
            // Navigacijos įgyvendinimas
        }
    }
    
  2. Vienkartiniai įvykiai būsenoje:

    Mažiau pageidaujamas požiūris, bet kartais naudojamas paprastiems atvejams. Pridedame žymeklį arba lauką UiState, kuris nurodo vienkartinį įvykį. Po įvykio apdorojimo View, ViewModel gauna naują Intent, kad išvalytų šį žymeklį/lauką. Tai gali apsunkinti logiką ir sukelti pakartotinį įvykdymą dėl netinkamo apdorojimo arba perkūrimo:

    // Pavyzdys su būsena ir vienkartiniu žymekliu
    data class MyUiState(
        val data: List<Item> = emptyList(),
        val isLoading: Boolean = false,
        val showSuccessToast: Boolean = false // Žymeklis vienkartiniam įvykiui
    )
    
    // ViewModel'e po sėkmingo veiksmo
    fun processSuccess() {
        _uiState.update { it.copy(showSuccessToast = true) }
    }
    
    // View'e, kai showSuccessToast = true
    if (state.showSuccessToast) {
        showToast("Sėkmingai!")
        // Svarbu: išvalyti žymeklį, siunčiant Intent
        viewModel.onToastShown()
    }
    
    // ViewModel'e žymeklio išvalymas
    fun onToastShown() {
        _uiState.update { it.copy(showSuccessToast = false) }
    }
    

Modernus Android su Flow ir MVI geriausias būdas — naudoti Side Effects per Channel. Tai švarus, saugus ir prognozuojamas sprendimas vienkartiniams įvykiams.