Назад к вопросам
Middle+
78
questionbank

Как в архитектуре MVI обрабатывать события, которые не требуется сохранять?

Sobes Copilot

Получайте ответы в реальном времени

Ответ от нейросети

sobes.tech AI

Для обработки одноразовых событий (например, отображение тоста, навигация) в 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) {
            // Реализация отображения тоста
        }
    
        private fun navigateToNextScreen() {
            // Реализация навигации
        }
    }
    
    
  2. Single Use Events в State:

    Менее предпочтительный вариант, но иногда используется в простых случаях. Добавляем флаг или поле в само состояние UiState, которое указывает на наличие одноразового события. После обработки события во View, View отправляет новый Intent ViewModel'и, чтобы сбросить этот флаг/поле в состоянии. Это может усложнить логику и потенциально приводить к повторному срабатыванию при неправильной обработке или пересоздании View.

    // Пример State с одноразовым флагом
    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) }
    }
    

Наиболее recommended подход в современном Android с Flow и MVI - использование Side Effects через Channel. Это чистое, безопасное и предсказуемое решение для одноразовых событий.