Sobes.tech
Middle+

Kako u arhitekturi MVI obraditi događaje koje nije potrebno sačuvati?

sobes.tech АИ

Одговор од АИ

За обраду од једнократних догађаја (нпр. приказивање Toast-а, навигација) у MVI-ју, који не треба да преживе поновно креирање View-а или да се чувају у стању, користе се различити приступи:

  1. Side Effects (SingleLiveEvent / Channels):

    Посебан механизам за слање догађаја из ViewModel-а у View. SingleLiveEvent (у старијим пројектима или библиотекама као што је androidx.lifecycle:lifecycle-livedata-ktx), или Channel из Flow-а (у савременим пројектима). Они гарантују да ће догађај бити обрађен само једном.

    // ViewModel са Flow и 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() {
            // Бизнис логика...
            viewModelScope.launch {
                _sideEffect.send(SideEffect.ShowToast("Операција успешно завршена!"))
            }
        }
    }
    
    sealed class SideEffect {
        data class ShowToast(val message: String) : SideEffect()
        object NavigateNext : SideEffect()
    }
    
    // У View-у (Fragment/Activity) пратимо 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-а
        }
    
        private fun navigateToNextScreen() {
            // Реализација навигације
        }
    }
    
  2. Једнократни догађаји у стању:

    Мање препоручен приступ, али се понекад користи у једноставним случајевима. Додаје се флаг или поље у саму UiState које указује на постојање једнократног догађаја. Након обраде догађаја у View-у, View шаље нови Intent ViewModel-у да ресетује овај флаг/поље у стању. Ово може компликовати логику и довести до поновног активирања у случају неправилног руковања или поновног креирања View-а.

    // Пример стања са једнократним флагом
    data class MyUiState(
        val data: List<Item> = emptyList(),
        val isLoading: Boolean = false,
        val showSuccessToast: Boolean = false // Флаг за једнократно догађај
    )
    
    // У ViewModel-у при успешном извршењу
    fun processSuccess() {
        _uiState.update { it.copy(showSuccessToast = true) }
    }
    
    // У View-у при обради showSuccessToast = true
    if (state.showSuccessToast) {
        showToast("Успешно!")
        // Важно: пошаљи Intent за ресетовање флага
        viewModel.onToastShown()
    }
    
    // У ViewModel-у за ресетовање флага
    fun onToastShown() {
        _uiState.update { it.copy(showSuccessToast = false) }
    }
    

Најбољи приступ у савременом Android-у са Flow и MVI-јем је коришћење Side Effects преко Channel. Ово је чисто, безбедно и предвидљиво решење за једнократна догађаји.