Как изменить параметр State'а в MVI
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Изменение состояния в MVI (Model-View-Intent)
В архитектуре MVI (Model-View-Intent) изменение состояния происходит через предсказуемый односторонний поток данных. В отличие от MVP или MVVM, состояние представлено immutable-объектом, который нельзя изменить напрямую. Вместо этого создаётся новое состояние на основе предыдущего.
Основной цикл MVI
- View отправляет Intent (намерение пользователя).
- Intent преобразуется в Action и обрабатывается в Model (обычно ViewModel или Interactor).
- Model генерирует новое State на основе предыдущего состояния.
- 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 на основе предыдущего, что обеспечивает предсказуемость и простоту отладки приложения.