← Назад к вопросам

Как изменить параметр State'а в MVI

2.0 Middle🔥 131 комментариев
#Архитектура и паттерны

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Изменение состояния в MVI (Model-View-Intent)

В архитектуре MVI (Model-View-Intent) изменение состояния происходит через предсказуемый односторонний поток данных. В отличие от MVP или MVVM, состояние представлено immutable-объектом, который нельзя изменить напрямую. Вместо этого создаётся новое состояние на основе предыдущего.

Основной цикл MVI

  1. View отправляет Intent (намерение пользователя).
  2. Intent преобразуется в Action и обрабатывается в Model (обычно ViewModel или Interactor).
  3. Model генерирует новое State на основе предыдущего состояния.
  4. View получает новое состояние и отображает его.

Практическая реализация изменения состояния

В современном Android-стеке с Kotlin и Coroutines типичная реализация включает:

// 1. Определение immutable State
data class TaskState(
    val tasks: List<Task> = emptyList(),
    val isLoading: Boolean = false,
    val error: String? = null,
    val filter: FilterType = FilterType.ALL
)

// 2. Определение sealed class для Intent/Action
sealed class TaskIntent {
    object LoadTasks : TaskIntent()
    data class DeleteTask(val id: String) : TaskIntent()
    data class UpdateFilter(val filter: FilterType) : TaskIntent()
}

// 3. ViewModel с StateFlow
class TaskViewModel(private val repository: TaskRepository) : ViewModel() {
    
    private val _state = MutableStateFlow(TaskState())
    val state: StateFlow<TaskState> = _state.asStateFlow()
    
    private val intents = MutableSharedFlow<TaskIntent>()
    
    init {
        collectIntents()
    }
    
    private fun collectIntents() {
        viewModelScope.launch {
            intents.collect { intent ->
                handleIntent(intent)
            }
        }
    }
    
    fun processIntent(intent: TaskIntent) {
        viewModelScope.launch {
            intents.emit(intent)
        }
    }
    
    private fun handleIntent(intent: TaskIntent) {
        when (intent) {
            is TaskIntent.LoadTasks -> loadTasks()
            is TaskIntent.DeleteTask -> deleteTask(intent.id)
            is TaskIntent.UpdateFilter -> updateFilter(intent.filter)
        }
    }
    
    private fun loadTasks() {
        // Создаем новое состояние на основе предыдущего
        _state.update { currentState ->
            currentState.copy(isLoading = true, error = null)
        }
        
        viewModelScope.launch {
            repository.getTasks()
                .onSuccess { tasks ->
                    _state.update { currentState ->
                        currentState.copy(
                            tasks = tasks,
                            isLoading = false
                        )
                    }
                }
                .onFailure { error ->
                    _state.update { currentState ->
                        currentState.copy(
                            isLoading = false,
                            error = error.message
                        )
                    }
                }
        }
    }
    
    private fun deleteTask(id: String) {
        _state.update { currentState ->
            currentState.copy(isLoading = true)
        }
        
        viewModelScope.launch {
            repository.deleteTask(id)
                .onSuccess {
                    // Удаляем задачу из списка
                    val newTasks = _state.value.tasks.filter { it.id != id }
                    _state.update { currentState ->
                        currentState.copy(
                            tasks = newTasks,
                            isLoading = false
                        )
                    }
                }
                .onFailure { error ->
                    _state.update { currentState ->
                        currentState.copy(
                            isLoading = false,
                            error = "Failed to delete task"
                        )
                    }
                }
        }
    }
    
    private fun updateFilter(filter: FilterType) {
        // Простое изменение состояния без асинхронной логики
        _state.update { currentState ->
            currentState.copy(filter = filter)
        }
    }
}

Ключевые принципы изменения состояния:

Иммутабельность состояния — Все изменения происходят через создание нового объекта State с помощью copy() (для data class) или аналогичных методов.

Редуктор (Reducer) подход — Более чистая архитектура с явными редукторами:

// Reducer функция
fun taskReducer(currentState: TaskState, result: TaskResult): TaskState {
    return when (result) {
        is TaskResult.TasksLoaded -> 
            currentState.copy(tasks = result.tasks, isLoading = false)
        is TaskResult.Loading -> 
            currentState.copy(isLoading = true, error = null)
        is TaskResult.Error -> 
            currentState.copy(isLoading = false, error = result.message)
        is TaskResult.FilterUpdated -> 
            currentState.copy(filter = result.filter)
    }
}

Односторонний поток данных:

  • Пользовательское действие → Intent
  • Intent → Action
  • Action + Текущее State → Новое State
  • Новое State → Обновление UI

Преимущества такого подхода:

  • Предсказуемость — Состояние изменяется только в одном месте (ViewModel/Reducer)
  • Деблагируемость — Легко логировать все изменения состояния
  • Тестируемость — Reducer-функции являются чистыми функциями без побочных эффектов
  • Повторяемость — Можно сохранять и воспроизводить последовательности состояний

В Activity/Fragment подписываемся на изменения:

// В Activity/Fragment
lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.state.collect { state ->
            render(state)
        }
    }
}

private fun render(state: TaskState) {
    // Обновляем UI на основе состояния
    progressBar.isVisible = state.isLoading
    errorView.isVisible = state.error != null
    errorView.text = state.error
    adapter.submitList(state.tasks)
    
    // Применяем фильтр
    val filteredTasks = when (state.filter) {
        FilterType.COMPLETED -> state.tasks.filter { it.completed }
        FilterType.PENDING -> state.tasks.filter { !it.completed }
        FilterType.ALL -> state.tasks
    }
    // Обновляем RecyclerView
}

Таким образом, изменение параметра состояния в MVI всегда происходит через создание нового экземпляра State на основе предыдущего, что обеспечивает предсказуемость и простоту отладки приложения.