Skip to content

MVI 架构深度解析 🔄

单向数据流架构,Jetpack Compose 首选模式


一、MVI 架构核心概念

1.1 架构概述

MVI(Model-View-Intent)是一种基于单向数据流的架构模式,起源于 Flutter 的 Redux 模式,在 Android 领域随着 Jetpack Compose 的流行而得到广泛应用。

┌─────────────────────────────────────────────────────────┐
│                    MVI 架构核心                           │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  Intent (用户意图)                                       │
│       ↓                                                  │
│  ┌─────────────────────────────────────┐               │
│  │          ViewModel                  │               │
│  │  - 接收 Intent                       │               │
│  │  - 处理业务逻辑                      │               │
│  │  - 更新 State                        │               │
│  └─────────────────────────────────────┘               │
│       ↓                                                  │
│  State (不可变状态)                                       │
│       ↓                                                  │
│  View (UI 渲染)                                          │
│       ↓                                                  │
│  └─────────────── 用户交互 → Intent ───────────────────┘
│                                                         │
│  单向数据流:Intent → ViewModel → State → View → Intent │
│                                                         │
└─────────────────────────────────────────────────────────┘

1.2 核心组件

组件职责特点
Intent用户意图、事件不可变、瞬时
State应用状态不可变、单一数据源
ViewModel处理逻辑接收 Intent,返回新 State
ViewUI 渲染观察 State,触发 Intent

1.3 MVI vs 传统架构

传统架构 (双向通信):
┌─────────────────────────────────────┐
│  View  ←──────────────→  ViewModel  │
│   ↓              ↑           ↓      │
│   └────────── 双向绑定 ────────────┘
└─────────────────────────────────────┘

MVI 架构 (单向数据流):
┌─────────────────────────────────────┐
│  View  →  Intent  →  ViewModel      │
│   ↑                              ↓ │
│   └─────────── State ←─────────────┘
│     单向数据流:Intent → State → View │
└─────────────────────────────────────┘

二、MVI 实战实现

2.1 基础实现

kotlin
// ==================== 1. 定义 State ====================

// State 是不可变数据类
@JvmInline
value class UserId(val value: Int)

data class UserState(
    val userId: UserId? = null,
    val isLoading: Boolean = false,
    val user: User? = null,
    val error: String? = null,
    val events: List<UserEvent> = emptyList()
) {
    companion object {
        fun loading(): UserState {
            return UserState(isLoading = true)
        }
        
        fun success(user: User): UserState {
            return UserState(
                userId = user.id,
                isLoading = false,
                user = user
            )
        }
        
        fun error(message: String): UserState {
            return UserState(
                isLoading = false,
                error = message
            )
        }
    }
}

// ==================== 2. 定义 Intent ====================

sealed class UserIntent {
    data class LoadUser(val userId: Int) : UserIntent()
    object RefreshUser : UserIntent()
    data class UpdateUser(val user: User) : UserIntent()
    object ClearError : UserIntent()
    data class DeleteUser(val userId: Int) : UserIntent()
}

// ==================== 3. 定义 Event ====================

sealed class UserEvent {
    data class NavigateToDetail(val userId: Int) : UserEvent()
    data class ShowSnackbar(val message: String) : UserEvent()
    data class ShowDialog(val title: String, val message: String) : UserEvent()
    object NavigateBack : UserEvent()
}

// ==================== 4. ViewModel 实现 ====================

@HiltViewModel
class UserViewModel @Inject constructor(
    private val userRepository: UserRepository
) : ViewModel() {
    
    // 初始状态
    private val initialState = UserState()
    
    // StateFlow 持状态
    private val _state = MutableStateFlow(initialState)
    val state: StateFlow<UserState> = _state.asStateFlow()
    
    init {
        // 初始化时加载
        processIntent(UserIntent.LoadUser(1))
    }
    
    // 处理 Intent - 返回新的 State
    fun processIntent(intent: UserIntent) {
        when (intent) {
            is UserIntent.LoadUser -> loadUser(intent.userId)
            is UserIntent.RefreshUser -> refreshUser()
            is UserIntent.UpdateUser -> updateUser(intent.user)
            is UserIntent.ClearError -> clearError()
            is UserIntent.DeleteUser -> deleteUser(intent.userId)
        }
    }
    
    // 加载用户
    private fun loadUser(userId: Int) {
        viewModelScope.launch {
            // 更新状态为加载中
            _state.update { it.copy(isLoading = true, error = null) }
            
            try {
                val result = userRepository.getUser(userId)
                
                _state.update { currentState ->
                    when {
                        result.isSuccess -> {
                            val user = result.getOrThrow()
                            UserState.success(user)
                        }
                        else -> {
                            UserState.error("加载失败")
                        }
                    }
                }
            } catch (e: Exception) {
                _state.update { currentState ->
                    UserState.error(e.message ?: "未知错误")
                }
            }
        }
    }
    
    // 刷新用户
    private fun refreshUser() {
        val currentUserId = _state.value.userId
        if (currentUserId != null) {
            loadUser(currentUserId.value)
        }
    }
    
    // 更新用户
    private fun updateUser(user: User) {
        viewModelScope.launch {
            val result = userRepository.updateUser(user)
            
            _state.update { currentState ->
                when {
                    result.isSuccess -> {
                        currentState.copy(
                            user = user,
                            error = null
                        )
                    }
                    else -> {
                        currentState.copy(
                            error = "更新失败"
                        )
                    }
                }
            }
        }
    }
    
    // 清除错误
    private fun clearError() {
        _state.update { it.copy(error = null) }
    }
    
    // 删除用户
    private fun deleteUser(userId: Int) {
        viewModelScope.launch {
            _state.update { it.copy(events = it.events + UserEvent.ShowDialog(
                "确认删除",
                "确定要删除这个用户吗?"
            ))}
        }
    }
    
    // 处理事件(确认删除)
    fun handleEvent(event: UserEvent) {
        when (event) {
            is UserEvent.NavigateToDetail -> {
                // 处理导航
            }
            is UserEvent.ShowSnackbar -> {
                // 显示提示
            }
        }
    }
}

// ==================== 5. View 层实现 ====================

// Compose 实现
@Composable
fun UserScreen(
    viewModel: UserViewModel = hiltViewModel()
) {
    // 收集 State
    val currentState by viewModel.state.collectAsState()
    
    // 收集一次性事件
    LaunchedEffect(Unit) {
        // 处理事件逻辑
    }
    
    // 渲染 UI
    UserScreenContent(
        state = currentState,
        onIntent = { intent -> viewModel.processIntent(intent) }
    )
}

@Composable
fun UserScreenContent(
    state: UserState,
    onIntent: (UserIntent) -> Unit
) {
    // 加载中状态
    if (state.isLoading) {
        Box(
            modifier = Modifier.fillMaxSize(),
            contentAlignment = Alignment.Center
        ) {
            CircularProgressIndicator()
        }
        return
    }
    
    // 错误状态
    if (state.error != null) {
        Column(
            modifier = Modifier.fillMaxSize(),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center
        ) {
            Icon(Icons.Default.Error, contentDescription = null)
            Text(text = state.error ?: "出错了")
            Spacer(modifier = Modifier.height(16.dp))
            Button(onClick = { onIntent(UserIntent.RefreshUser) }) {
                Text("重试")
            }
            Spacer(modifier = Modifier.height(8.dp))
            TextButton(onClick = { onIntent(UserIntent.ClearError) }) {
                Text("忽略")
            }
        }
        return
    }
    
    // 数据显示
    state.user?.let { user ->
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(16.dp)
        ) {
            // 用户头像
            AsyncImage(
                model = user.avatar,
                contentDescription = "用户头像",
                modifier = Modifier
                    .size(80.dp)
                    .border(
                        BorderStroke(2.dp, Color.Gray),
                        RoundedCornerShape(50.dp)
                    )
            )
            
            Spacer(modifier = Modifier.height(16.dp))
            
            // 用户名
            Text(
                text = user.name,
                style = MaterialTheme.typography.h5
            )
            
            // 用户邮箱
            Text(
                text = user.email,
                style = MaterialTheme.typography.body1,
                color = Color.Gray
            )
            
            Spacer(modifier = Modifier.height(24.dp))
            
            // 操作按钮
            Row(
                horizontalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                Button(onClick = { onIntent(UserIntent.RefreshUser) }) {
                    Icon(Icons.Default.Refresh, contentDescription = null)
                }
                
                OutlinedButton(onClick = { 
                    onIntent(UserIntent.DeleteUser(user.id))
                }) {
                    Icon(Icons.Default.Delete, contentDescription = null)
                }
            }
        }
    } ?: run {
        // 空状态
        Box(
            modifier = Modifier.fillMaxSize(),
            contentAlignment = Alignment.Center
        ) {
            Text("暂无数据")
        }
    }
}

2.2 MVI + StateFlow + SharedFlow

kotlin
// ==================== MVI + StateFlow + SharedFlow ====================

@HiltViewModel
class UserViewModel @Inject constructor(
    private val userRepository: UserRepository
) : ViewModel() {
    
    // 状态 - StateFlow
    private val _state = MutableStateFlow(UserState())
    val state: StateFlow<UserState> = _state.asStateFlow()
    
    // 一次性事件 - SharedFlow
    private val _event = MutableSharedFlow<UserEvent>()
    val event: SharedFlow<UserEvent> = _event.asSharedFlow()
    
    init {
        processIntent(UserIntent.LoadUser(1))
    }
    
    fun processIntent(intent: UserIntent) {
        when (intent) {
            is UserIntent.LoadUser -> loadUser(intent.userId)
            is UserIntent.RefreshUser -> refreshUser()
            is UserIntent.UpdateUser -> updateUser(intent.user)
            is UserIntent.ClearError -> clearError()
            is UserIntent.DeleteUser -> deleteUser(intent.userId)
        }
    }
    
    private fun loadUser(userId: Int) {
        viewModelScope.launch {
            _state.update { it.copy(isLoading = true, error = null) }
            
            try {
                val user = userRepository.getUser(userId).getOrThrow()
                
                _state.update { currentState ->
                    currentState.copy(
                        isLoading = false,
                        user = user
                    )
                }
                
                // 发送成功事件
                _event.emit(UserEvent.ShowSnackbar("加载成功"))
            } catch (e: Exception) {
                _state.update { currentState ->
                    currentState.copy(
                        isLoading = false,
                        error = e.message
                    )
                }
            }
        }
    }
    
    private fun refreshUser() {
        val currentUserId = _state.value.userId ?: return
        loadUser(currentUserId)
    }
    
    private fun updateUser(user: User) {
        viewModelScope.launch {
            try {
                val result = userRepository.updateUser(user)
                
                if (result.isSuccess) {
                    _state.update { currentState ->
                        currentState.copy(user = user)
                    }
                    _event.emit(UserEvent.ShowSnackbar("更新成功"))
                }
            } catch (e: Exception) {
                _event.emit(UserEvent.ShowSnackbar("更新失败:${e.message}"))
            }
        }
    }
    
    private fun clearError() {
        _state.update { it.copy(error = null) }
    }
    
    private fun deleteUser(userId: Int) {
        // 先显示确认对话框
        _event.emit(UserEvent.ShowDialog(
            title = "确认删除",
            message = "确定要删除这个用户吗?"
        ))
        
        // 监听确认事件(需要从外部触发)
        // 实际使用中,应该由 UI 确认后再调用删除
    }
    
    // 处理确认删除
    fun confirmDelete() {
        val userId = _state.value.userId ?: return
        
        viewModelScope.launch {
            try {
                val result = userRepository.deleteUser(userId)
                
                if (result.isSuccess) {
                    _state.update { currentState ->
                        currentState.copy(
                            user = null,
                            userId = null
                        )
                    }
                    _event.emit(UserEvent.ShowSnackbar("删除成功"))
                } else {
                    _event.emit(UserEvent.ShowSnackbar("删除失败"))
                }
            } catch (e: Exception) {
                _event.emit(UserEvent.ShowSnackbar("删除失败:${e.message}"))
            }
        }
    }
}

// Compose 实现
@Composable
fun UserScreen(
    viewModel: UserViewModel = hiltViewModel(),
    snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
    navigator: Navigator? = null
) {
    val state by viewModel.state.collectAsStateWithLifecycle()
    val scope = rememberCoroutineScope()
    
    // 收集一次性事件
    LaunchedEffect(Unit) {
        viewModel.event.collect { event ->
            when (event) {
                is UserEvent.ShowSnackbar -> {
                    scope.launch {
                        snackbarHostState.showSnackbar(event.message)
                    }
                }
                is UserEvent.ShowDialog -> {
                    // 显示对话框
                }
                is UserEvent.NavigateToDetail -> {
                    navigator?.navigate("user/${event.userId}")
                }
                is UserEvent.NavigateBack -> {
                    navigator?.popBackStack()
                }
            }
        }
    }
    
    Scaffold(
        snackbarHost = { SnackbarHost(snackbarHostState) }
    ) { paddingValues ->
        UserScreenContent(
            state = state,
            onIntent = { intent -> viewModel.processIntent(intent) },
            onConfirmDelete = { viewModel.confirmDelete() },
            modifier = Modifier.padding(paddingValues)
        )
    }
}

@Composable
fun UserScreenContent(
    state: UserState,
    onIntent: (UserIntent) -> Unit,
    onConfirmDelete: () -> Unit,
    modifier: Modifier = Modifier
) {
    // 实现 UI 渲染逻辑
}

2.3 MVI 工具库:MVI-kt

kotlin
// ==================== 使用 MVI-kt 工具库 ====================

// MVI 基类
abstract class MviViewModel<State, Event, Intent> : ViewModel() where State : MviState {
    
    // 初始状态
    abstract fun initialState(): State
    
    // 处理 Intent 并返回新的 State
    abstract suspend fun handleIntent(intent: Intent): State
    
    // StateFlow
    private val _state = MutableStateFlow(initialState())
    val state: StateFlow<State> = _state.asStateFlow()
    
    // SharedFlow 用于一次性事件
    private val _event = MutableSharedFlow<Event>()
    val event: SharedFlow<Event> = _event.asSharedFlow()
    
    // 处理 Intent
    open fun processIntent(intent: Intent) {
        viewModelScope.launch {
            val newState = handleIntent(intent)
            _state.value = newState
        }
    }
    
    // 发送事件
    protected suspend fun emitEvent(event: Event) {
        _event.emit(event)
    }
    
    // 更新状态
    protected suspend fun updateState(update: State.() -> State) {
        _state.value = _state.value.update()
    }
}

// 具体实现
class UserViewModel @Inject constructor(
    private val userRepository: UserRepository
) : MviViewModel<UserState, UserEvent, UserIntent>() {
    
    override fun initialState(): UserState {
        return UserState()
    }
    
    override suspend fun handleIntent(intent: UserIntent): UserState {
        return when (intent) {
            is UserIntent.LoadUser -> loadUser(intent.userId)
            is UserIntent.RefreshUser -> refreshUser()
            is UserIntent.UpdateUser -> updateUser(intent.user)
            is UserIntent.ClearError -> clearError()
            is UserIntent.DeleteUser -> deleteUser(intent.userId)
        }
    }
    
    private suspend fun loadUser(userId: Int): UserState {
        return try {
            _state.value.copy(isLoading = true, error = null)
            
            val user = userRepository.getUser(userId).getOrThrow()
            
            _state.value.copy(
                isLoading = false,
                userId = userId,
                user = user
            ).also {
                emitEvent(UserEvent.ShowSnackbar("加载成功"))
            }
        } catch (e: Exception) {
            _state.value.copy(
                isLoading = false,
                error = e.message
            )
        }
    }
    
    private suspend fun refreshUser(): UserState {
        val userId = _state.value.userId ?: return _state.value
        return loadUser(userId)
    }
    
    private suspend fun updateUser(user: User): UserState {
        return try {
            val result = userRepository.updateUser(user)
            
            if (result.isSuccess) {
                _state.value.copy(user = user).also {
                    emitEvent(UserEvent.ShowSnackbar("更新成功"))
                }
            } else {
                _state.value.copy(error = "更新失败")
            }
        } catch (e: Exception) {
            _state.value.copy(error = e.message)
        }
    }
    
    private suspend fun clearError(): UserState {
        return _state.value.copy(error = null)
    }
    
    private suspend fun deleteUser(userId: Int): UserState {
        emitEvent(UserEvent.ShowDialog("确认删除", "确定删除吗?"))
        return _state.value
    }
}

2.4 MVI + Unidata (Turbine)

kotlin
// ==================== MVI + Unidata 模式 ====================

// 定义 State 更新器
sealed class UserStateReducer {
    object Initial : UserStateReducer()
    data class Loading(val isLoading: Boolean) : UserStateReducer()
    data class UserLoaded(val user: User) : UserStateReducer()
    data class Error(val error: String) : UserStateReducer()
    object ErrorCleared : UserStateReducer()
}

// 使用 StateFlow 的 reducer 模式
class UserViewModel @Inject constructor(
    private val userRepository: UserRepository
) : ViewModel() {
    
    // State
    private val _state = MutableStateFlow(UserState())
    val state: StateFlow<UserState> = _state.asStateFlow()
    
    // Event
    private val _event = MutableSharedFlow<UserEvent>()
    val event: SharedFlow<UserEvent> = _event.asSharedFlow()
    
    init {
        processIntent(UserIntent.LoadUser(1))
    }
    
    fun processIntent(intent: UserIntent) {
        when (intent) {
            is UserIntent.LoadUser -> loadUser(intent.userId)
            is UserIntent.RefreshUser -> refreshUser()
            is UserIntent.UpdateUser -> updateUser(intent.user)
            is UserIntent.ClearError -> clearError()
            is UserIntent.DeleteUser -> deleteUser(intent.userId)
        }
    }
    
    private fun loadUser(userId: Int) {
        viewModelScope.launch {
            // 使用 reduce 更新状态
            reduce(UserStateReducer.Loading(true))
            
            try {
                val user = userRepository.getUser(userId).getOrThrow()
                reduce(UserStateReducer.UserLoaded(user))
                _event.emit(UserEvent.ShowSnackbar("加载成功"))
            } catch (e: Exception) {
                reduce(UserStateReducer.Loading(false))
                reduce(UserStateReducer.Error(e.message ?: "未知错误"))
            }
        }
    }
    
    private fun reduce(reducer: UserStateReducer) {
        _state.update { currentState ->
            when (reducer) {
                is UserStateReducer.Initial -> currentState
                is UserStateReducer.Loading -> {
                    currentState.copy(isLoading = reducer.isLoading)
                }
                is UserStateReducer.UserLoaded -> {
                    currentState.copy(
                        user = reducer.user,
                        isLoading = false,
                        error = null
                    )
                }
                is UserStateReducer.Error -> {
                    currentState.copy(
                        error = reducer.error,
                        isLoading = false
                    )
                }
                is UserStateReducer.ErrorCleared -> {
                    currentState.copy(error = null)
                }
            }
        }
    }
    
    private fun refreshUser() {
        val userId = _state.value.userId ?: return
        loadUser(userId)
    }
    
    private fun updateUser(user: User) {
        viewModelScope.launch {
            try {
                val result = userRepository.updateUser(user)
                if (result.isSuccess) {
                    reduce(UserStateReducer.UserLoaded(user))
                    _event.emit(UserEvent.ShowSnackbar("更新成功"))
                }
            } catch (e: Exception) {
                reduce(UserStateReducer.Error(e.message ?: "更新失败"))
            }
        }
    }
    
    private fun clearError() {
        reduce(UserStateReducer.ErrorCleared)
    }
    
    private fun deleteUser(userId: Int) {
        _event.emit(UserEvent.ShowDialog("确认删除", "确定删除吗?"))
    }
}

三、MVI 进阶模式

3.1 复合 State 处理

kotlin
// ==================== 复合 State 处理 ====================

// 使用 sealed class 定义状态机
sealed class ScreenState {
    object Initial : ScreenState()
    object Loading : ScreenState()
    data class Success<T>(val data: T) : ScreenState()
    data class Error(val message: String, val retry: () -> Unit = {}) : ScreenState()
    object Empty : ScreenState()
}

// State 包含多个子状态
data class UserState(
    val userScreen: ScreenState = ScreenState.Initial,
    val commentsScreen: ScreenState = ScreenState.Initial,
    val searchQuery: String = "",
    val isSearching: Boolean = false
)

// ViewModel 处理复合状态
class UserViewModel @Inject constructor(
    private val userRepository: UserRepository,
    private val commentRepository: CommentRepository
) : ViewModel() {
    
    private val _state = MutableStateFlow(UserState())
    val state: StateFlow<UserState> = _state.asStateFlow()
    
    fun loadUser(userId: Int) {
        viewModelScope.launch {
            _state.update { it.copy(userScreen = ScreenState.Loading) }
            
            try {
                val user = userRepository.getUser(userId).getOrThrow()
                _state.update { it.copy(userScreen = ScreenState.Success(user)) }
            } catch (e: Exception) {
                _state.update { 
                    it.copy(userScreen = ScreenState.Error(
                        e.message ?: "加载失败",
                        retry = { loadUser(userId) }
                    ))
                }
            }
        }
    }
    
    fun loadComments(userId: Int) {
        viewModelScope.launch {
            _state.update { it.copy(commentsScreen = ScreenState.Loading) }
            
            try {
                val comments = commentRepository.getComments(userId)
                if (comments.isEmpty()) {
                    _state.update { it.copy(commentsScreen = ScreenState.Empty) }
                } else {
                    _state.update { it.copy(commentsScreen = ScreenState.Success(comments)) }
                }
            } catch (e: Exception) {
                _state.update { 
                    it.copy(commentsScreen = ScreenState.Error(e.message ?: "加载失败"))
                }
            }
        }
    }
    
    fun search(query: String) {
        _state.update { it.copy(searchQuery = query, isSearching = true) }
        // 搜索逻辑
    }
}

// Compose 渲染
@Composable
fun UserScreen(viewModel: UserViewModel = hiltViewModel()) {
    val state by viewModel.state.collectAsState()
    
    Column {
        // 渲染用户状态
        when (val userState = state.userScreen) {
            is ScreenState.Loading -> {
                CircularProgressIndicator()
            }
            is ScreenState.Success<*> -> {
                val user = userState.data as User
                UserCard(user = user)
            }
            is ScreenState.Error -> {
                ErrorView(
                    message = userState.message,
                    onRetry = userState.retry
                )
            }
            else -> {}
        }
        
        // 渲染评论状态
        when (val commentsState = state.commentsScreen) {
            is ScreenState.Loading -> {
                LinearProgressIndicator()
            }
            is ScreenState.Success<*> -> {
                val comments = commentsState.data as List<Comment>
                CommentsList(comments = comments)
            }
            is ScreenState.Empty -> {
                EmptyView(message = "暂无评论")
            }
            is ScreenState.Error -> {
                ErrorView(
                    message = commentsState.message,
                    onRetry = commentsState.retry
                )
            }
            else -> {}
        }
    }
}

3.2 MVI 状态持久化

kotlin
// ==================== MVI 状态持久化 ====================

class UserViewModel @Inject constructor(
    private val userRepository: UserRepository,
    private val preferences: UserPreferences
) : ViewModel() {
    
    private val _state = MutableStateFlow(UserState())
    val state: StateFlow<UserState> = _state.asStateFlow()
    
    // 持久化状态
    private val _savedState = MutableStateFlow<UserState?>(null)
    
    init {
        // 恢复之前保存的状态
        restoreState()
    }
    
    private fun restoreState() {
        viewModelScope.launch {
            val saved = preferences.getLastState()
            if (saved != null) {
                _state.value = saved
                _savedState.value = saved
            }
        }
    }
    
    // 保存状态
    private suspend fun saveState(state: UserState) {
        // 只保存关键状态,不保存临时状态
        val savedState = state.copy(
            isLoading = false,
            error = null
        )
        preferences.saveState(savedState)
        _savedState.value = savedState
    }
    
    fun processIntent(intent: UserIntent) {
        when (intent) {
            is UserIntent.LoadUser -> loadUser(intent.userId)
            is UserIntent.RefreshUser -> refreshUser()
            // ...
        }
    }
    
    private fun loadUser(userId: Int) {
        viewModelScope.launch {
            _state.update { it.copy(isLoading = true, error = null) }
            
            try {
                val user = userRepository.getUser(userId).getOrThrow()
                
                val newState = _state.value.copy(
                    isLoading = false,
                    user = user
                )
                
                _state.value = newState
                saveState(newState)
            } catch (e: Exception) {
                _state.update { 
                    it.copy(isLoading = false, error = e.message)
                }
            }
        }
    }
    
    // 清除保存的状态
    fun clearSavedState() {
        viewModelScope.launch {
            preferences.clearState()
            _savedState.value = null
        }
    }
    
    override fun onCleared() {
        super.onCleared()
        //  ViewModel 销毁时保存状态
        _savedState.value?.let { saveState(it) }
    }
}

// 状态存储
interface UserPreferences {
    suspend fun saveState(state: UserState)
    suspend fun getLastState(): UserState?
    suspend fun clearState()
}

class UserPreferencesImpl @Inject constructor(
    private val dataStore: DataStore<Preferences>
) : UserPreferences {
    
    companion object {
        private val STATE_KEY = Preferences.StringKeys("user_state")
    }
    
    override suspend fun saveState(state: UserState) {
        val json = Json.encodeToString(UserState.serializer(), state)
        dataStore.edit { prefs ->
            prefs[STATE_KEY] = json
        }
    }
    
    override suspend fun getLastState(): UserState? {
        return try {
            val json = dataStore.data.map { prefs ->
                prefs[STATE_KEY]
            }.firstOrNull()
            
            json?.let { Json.decodeFromString(UserState.serializer(), it) }
        } catch (e: Exception) {
            null
        }
    }
    
    override suspend fun clearState() {
        dataStore.edit { prefs ->
            prefs.remove(STATE_KEY)
        }
    }
}

3.3 MVI 状态管理优化

kotlin
// ==================== MVI 状态管理优化 ====================

// 1. 状态合并
class OptimizedUserViewModel @Inject constructor(
    private val userRepository: UserRepository
) : ViewModel() {
    
    // 使用 StateFlow 的 update 进行状态合并
    private val _state = MutableStateFlow(UserState())
    val state: StateFlow<UserState> = _state.asStateFlow()
    
    fun loadUser(userId: Int) {
        viewModelScope.launch {
            // 原子更新,避免竞态条件
            _state.update { currentState ->
                currentState.copy(isLoading = true, error = null)
            }
            
            try {
                val user = userRepository.getUser(userId).getOrThrow()
                
                _state.update { currentState ->
                    // 检查是否是最新的请求
                    if (currentState.lastRequestId == userId) {
                        currentState.copy(
                            isLoading = false,
                            user = user,
                            lastRequestId = userId
                        )
                    } else {
                        currentState
                    }
                }
            } catch (e: Exception) {
                _state.update { currentState ->
                    if (currentState.lastRequestId == userId) {
                        currentState.copy(
                            isLoading = false,
                            error = e.message,
                            lastRequestId = userId
                        )
                    } else {
                        currentState
                    }
                }
            }
        }
    }
}

// 2. 防抖处理
class DebounceUserViewModel @Inject constructor(
    private val userRepository: UserRepository
) : ViewModel() {
    
    private val _state = MutableStateFlow(UserState())
    val state: StateFlow<UserState> = _state.asStateFlow()
    
    // 搜索防抖
    private val searchJob = MutableSharedFlow<String>()
    
    init {
        viewModelScope.launch {
            searchJob
                .debounce(300)
                .distinctUntilChanged()
                .collect { query ->
                    searchUsers(query)
                }
        }
    }
    
    fun onSearchQuery(query: String) {
        // 更新查询状态
        _state.update { it.copy(searchQuery = query) }
        
        // 发送搜索请求
        viewModelScope.launch {
            searchJob.emit(query)
        }
    }
    
    private suspend fun searchUsers(query: String) {
        if (query.isBlank()) {
            _state.update { it.copy(users = emptyList()) }
            return
        }
        
        _state.update { it.copy(isSearching = true) }
        
        try {
            val users = userRepository.searchUsers(query)
            _state.update { it.copy(isSearching = false, users = users) }
        } catch (e: Exception) {
            _state.update { it.copy(isSearching = false, error = e.message) }
        }
    }
}

// 3. 状态缓存
class CachedUserViewModel @Inject constructor(
    private val userRepository: UserRepository
) : ViewModel() {
    
    private val _state = MutableStateFlow(UserState())
    val state: StateFlow<UserState> = _state.asStateFlow()
    
    // 本地缓存
    private val cache = ConcurrentHashMap<Int, User>()
    
    fun loadUser(userId: Int) {
        // 先查缓存
        val cached = cache[userId]
        if (cached != null && isCacheValid(cached)) {
            _state.update { it.copy(user = cached, isLoading = false) }
        }
        
        // 异步刷新
        viewModelScope.launch {
            try {
                val user = userRepository.getUser(userId).getOrThrow()
                cache[userId] = user
                
                _state.update { 
                    it.copy(user = user, isLoading = false)
                }
            } catch (e: Exception) {
                _state.update { 
                    it.copy(isLoading = false, error = e.message)
                }
            }
        }
    }
    
    private fun isCacheValid(user: User): Boolean {
        val cacheTime = user.cacheTime ?: return false
        val now = System.currentTimeMillis()
        return now - cacheTime < CACHE_DURATION_MS
    }
}

四、MVI vs MVVM 深度对比

4.1 架构对比

┌─────────────────────────────────────────────────────────┐
│                    MVI vs MVVM 对比                      │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  MVVM:                                                  │
│  ┌─────────┐    ┌─────────────┐    ┌─────────┐        │
│  │  View   │───→│  ViewModel  │───→│  Model  │        │
│  │         │    │             │    │         │        │
│  └─────────┘    └─────────────┘    └─────────┘        │
│      ↑              │                                  │
│      └──────────────┘                                  │
│         LiveData/StateFlow (双向)                      │
│                                                         │
│  MVI:                                                   │
│  ┌─────────┐    ┌─────────────┐    ┌─────────┐        │
│  │  View   │───→│   Intent    │───→│ ViewModel│       │
│  │         │    │             │    │         │        │
│  └─────────┘    └─────────────┘    └─────────┘        │
│      ↑                                      │          │
│      │            ┌─────────┐               │          │
│      └────────────│  State  │←──────────────┘          │
│                   └─────────┘                          │
│                   单向数据流                           │
│                                                         │
└─────────────────────────────────────────────────────────┘

4.2 核心差异

维度MVVMMVI
数据流双向绑定单向数据流
状态管理LiveData/StateFlow不可变 State
事件处理LiveData/FlowSharedFlow/Event
State 可变更性可变不可变
调试难度中等容易(State 可追溯)
代码量较少中等
学习曲线平缓稍陡
适用场景传统 View、简单交互Compose、复杂交互
性能优秀优秀
可测试性良好优秀

4.3 代码对比

kotlin
// ==================== MVVM 实现 ====================

@HiltViewModel
class UserViewModelMVVM @Inject constructor(
    private val userRepository: UserRepository
) : ViewModel() {
    
    // 多个独立的状态
    private val _isLoading = MutableStateFlow(false)
    val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
    
    private val _user = MutableStateFlow<User?>(null)
    val user: StateFlow<User?> = _user.asStateFlow()
    
    private val _error = MutableStateFlow<String?>(null)
    val error: StateFlow<String?> = _error.asStateFlow()
    
    fun loadUser(userId: Int) {
        viewModelScope.launch {
            _isLoading.value = true
            
            try {
                val user = userRepository.getUser(userId).getOrThrow()
                _user.value = user
                _error.value = null
            } catch (e: Exception) {
                _error.value = e.message
            }
            
            _isLoading.value = false
        }
    }
    
    fun clearError() {
        _error.value = null
    }
}

// Compose 渲染 - 需要组合多个 State
@Composable
fun UserScreenMVVM(viewModel: UserViewModelMVVM) {
    val isLoading by viewModel.isLoading.collectAsState()
    val user by viewModel.user.collectAsState()
    val error by viewModel.error.collectAsState()
    
    // 需要手动组合状态
    if (isLoading) {
        CircularProgressIndicator()
    } else if (error != null) {
        ErrorView(error)
    } else if (user != null) {
        UserCard(user)
    }
}

// ==================== MVI 实现 ====================

@HiltViewModel
class UserViewModelMVI @Inject constructor(
    private val userRepository: UserRepository
) : ViewModel() {
    
    // 单一状态
    private val _state = MutableStateFlow(UserState())
    val state: StateFlow<UserState> = _state.asStateFlow()
    
    fun loadUser(userId: Int) {
        viewModelScope.launch {
            _state.update { it.copy(isLoading = true, error = null) }
            
            try {
                val user = userRepository.getUser(userId).getOrThrow()
                _state.update { it.copy(isLoading = false, user = user) }
            } catch (e: Exception) {
                _state.update { it.copy(isLoading = false, error = e.message) }
            }
        }
    }
}

// Compose 渲染 - 单一 State 源
@Composable
fun UserScreenMVI(viewModel: UserViewModelMVI) {
    val state by viewModel.state.collectAsState()
    
    // 状态自然组合
    when {
        state.isLoading -> CircularProgressIndicator()
        state.error != null -> ErrorView(state.error)
        state.user != null -> UserCard(state.user)
    }
}

五、MVI 最佳实践

5.1 状态设计原则

kotlin
// ==================== 状态设计原则 ====================

// ✅ 原则 1: State 应该是不可变的
data class UserState(
    val isLoading: Boolean = false,
    val user: User? = null,
    val error: String? = null
)

// ✅ 原则 2: 使用 data class 自动实现 equals/hashCode
// ✅ 原则 3: 提供合理的默认值
// ✅ 原则 4: 使用 sealed class 定义状态类型

// ✅ 原则 5: State 应该包含渲染所需的所有数据
data class UserListState(
    val users: List<User> = emptyList(),
    val isLoading: Boolean = false,
    val isLoadingMore: Boolean = false,
    val hasMore: Boolean = true,
    val error: String? = null,
    val currentPage: Int = 0
)

// ✅ 原则 6: 使用 companion object 提供工厂方法
data class UserState(
    val user: User? = null
) {
    companion object {
        fun initial() = UserState()
        fun loading() = UserState(isLoading = true)
        fun success(user: User) = UserState(user = user)
        fun error(message: String) = UserState(error = message)
    }
}

5.2 Event 处理模式

kotlin
// ==================== Event 处理模式 ====================

// 模式 1: Event 在 State 中(不推荐,Event 会留存)
data class UserState(
    val events: List<Event> = emptyList()
)

// 模式 2: 使用 SharedFlow(推荐)
class UserViewModel {
    private val _event = MutableSharedFlow<UserEvent>()
    val event: SharedFlow<UserEvent> = _event.asSharedFlow()
    
    fun navigateToDetail(userId: Int) {
        viewModelScope.launch {
            _event.emit(UserEvent.NavigateToDetail(userId))
        }
    }
}

// 模式 3: 使用 SingleEvent 包装
class SingleEvent<T>(private val content: T) {
    private var hasBeenHandled = false
    
    fun getContentIfNotHandled(): T? {
        return if (hasBeenHandled) {
            null
        } else {
            hasBeenHandled = true
            content
        }
    }
    
    fun peekContent(): T = content
}

data class UserState(
    val navigateEvent: SingleEvent<Int>? = null
)

// 模式 4: StateFlow + 事件收集
class UserViewModel {
    private val _state = MutableStateFlow(UserState())
    val state: StateFlow<UserState> = _state.asStateFlow()
    
    fun showSnackbar(message: String) {
        _state.update { 
            it.copy(showSnackbarEvent = SingleEvent(message))
        }
        
        // 延迟清除
        viewModelScope.launch {
            delay(3000)
            _state.update { 
                it.copy(showSnackbarEvent = null)
            }
        }
    }
}

// Compose 收集事件
@Composable
fun UserScreen(viewModel: UserViewModel) {
    val state by viewModel.state.collectAsState()
    
    // 收集 Snackbar 事件
    LaunchedEffect(state.showSnackbarEvent) {
        state.showSnackbarEvent?.getContentIfNotHandled()?.let { message ->
            // 显示 Snackbar
        }
    }
}

5.3 错误处理

kotlin
// ==================== 错误处理 ====================

// 1. 定义统一错误类型
sealed class AppError {
    data class NetworkError(val code: Int, val message: String) : AppError()
    data class ServerError(val code: Int, val message: String) : AppError()
    data class ParseError(val message: String) : AppError()
    data class UnknownError(val message: String) : AppError()
}

// 2. State 中包含错误
data class UserState(
    val error: AppError? = null
)

// 3. 错误处理策略
class UserViewModel {
    private val _state = MutableStateFlow(UserState())
    
    fun loadUser(userId: Int) {
        viewModelScope.launch {
            try {
                val user = userRepository.getUser(userId)
                _state.update { it.copy(user = user, error = null) }
            } catch (e: HttpException) {
                val appError = AppError.NetworkError(
                    code = e.code(),
                    message = getErrorMessage(e.code())
                )
                _state.update { it.copy(error = appError) }
            } catch (e: Exception) {
                _state.update { 
                    it.copy(error = AppError.UnknownError(e.message ?: "未知错误"))
                }
            }
        }
    }
    
    private fun getErrorMessage(code: Int): String {
        return when (code) {
            401 -> "未授权,请重新登录"
            403 -> "无权访问"
            404 -> "用户不存在"
            500 -> "服务器错误"
            else -> "请求失败"
        }
    }
}

// 4. 错误重试机制
data class UserState(
    val error: AppError? = null,
    val retryCount: Int = 0
) {
    companion object {
        const val MAX_RETRY_COUNT = 3
    }
}

class UserViewModel {
    fun loadUser(userId: Int) {
        viewModelScope.launch {
            var retryCount = 0
            
            while (retryCount < UserState.MAX_RETRY_COUNT) {
                try {
                    val user = userRepository.getUser(userId)
                    _state.update { it.copy(user = user, error = null, retryCount = 0) }
                    return@launch
                } catch (e: Exception) {
                    retryCount++
                    
                    if (retryCount >= UserState.MAX_RETRY_COUNT) {
                        _state.update { 
                            it.copy(error = e.toAppError(), retryCount = retryCount)
                        }
                        break
                    }
                    
                    // 指数退避
                    delay(1000L * retryCount)
                }
            }
        }
    }
}

六、面试核心考点

6.1 基础问题

Q1: MVI 架构的核心思想?

A:

MVI = Model-View-Intent

核心思想:
1. 单向数据流:Intent → ViewModel → State → View
2. 不可变 State:所有状态更新都返回新的 State 对象
3. 单一数据源:State 是 UI 的唯一数据源
4. Intent 驱动:所有用户交互都通过 Intent 触发

优势:
- 状态可追溯,易于调试
- 代码结构清晰,易于维护
- 天然支持热重载(Hot Reload)
- 与 Jetpack Compose 完美契合

Q2: MVI 中的 State 为什么要不可变?

A:

不可变 State 的优势:
1. 线程安全:不可变对象天生线程安全
2. 状态追溯:每次更新都创建新对象,可追溯状态变化
3. 简化 diff:Compose 可以通过引用比较判断是否需要重新渲染
4. 避免副作用:无法意外修改状态

实现方式:
data class UserState(
    val user: User? = null  // 使用 val 而非 var
)

更新状态:
_state.update { currentState ->
    currentState.copy(user = newUser)  // 返回新对象
}

Q3: MVI 如何处理一次性事件(如导航、Snackbar)?

A:

kotlin
// 方式 1: SharedFlow
class UserViewModel {
    private val _event = MutableSharedFlow<UserEvent>()
    val event: SharedFlow<UserEvent> = _event.asSharedFlow()
    
    fun showSnackbar(message: String) {
        viewModelScope.launch {
            _event.emit(UserEvent.ShowSnackbar(message))
        }
    }
}

// 方式 2: SingleEvent
class SingleEvent<T>(private val content: T) {
    private var hasBeenHandled = false
    
    fun getContentIfNotHandled(): T? {
        return if (hasBeenHandled) null else {
            hasBeenHandled = true
            content
        }
    }
}

// 方式 3: StateFlow + LaunchedEffect
data class UserState(
    val showSnackbar: String? = null
)

@Composable
fun Screen(viewModel: UserViewModel) {
    val state by viewModel.state.collectAsState()
    
    LaunchedEffect(state.showSnackbar) {
        state.showSnackbar?.let { showSnackbar(it) }
    }
}

Q4: MVI vs MVVM 如何选择?

A:

选择 MVI:
- 使用 Jetpack Compose
- 需要复杂的状态管理
- 需要状态可追溯
- 团队熟悉不可变数据

选择 MVVM:
- 使用传统 View
- 简单的项目
- 团队更熟悉 LiveData
- 性能要求极高(State 创建开销)

6.2 进阶问题

Q5: MVI 如何处理状态更新的性能问题?

A:

kotlin
// 问题:频繁创建新 State 对象可能影响性能

// 优化 1: 只在必要时更新
private fun updateState(update: State.() -> State) {
    val newState = _state.value.update()
    if (newState != _state.value) {
        _state.value = newState
    }
}

// 优化 2: 使用 StateFlow 的 update(原子操作)
_state.update { currentState ->
    currentState.copy(user = user)
}

// 优化 3: 批量更新
private fun batchUpdate(updates: List<(UserState) -> UserState>) {
    val newState = updates.fold(_state.value) { state, update ->
        update(state)
    }
    _state.value = newState
}

// 优化 4: 使用@Stable 标记
@Stable
data class UserState(
    val user: User? = null
)

Q6: MVI 如何实现撤销/重做功能?

A:

kotlin
// 使用命令模式实现撤销/重做
class CommandManager<T> {
    private val undoStack = ArrayDeque<Command<T>>()
    private val redoStack = ArrayDeque<Command<T>>()
    
    fun execute(command: Command<T>) {
        command.execute()
        undoStack.push(command)
        redoStack.clear()
    }
    
    fun undo() {
        val command = undoStack.popOrNull() ?: return
        command.undo()
        redoStack.push(command)
    }
    
    fun redo() {
        val command = redoStack.popOrNull() ?: return
        command.execute()
        undoStack.push(command)
    }
}

interface Command<T> {
    fun execute()
    fun undo()
}

class UserEditCommand(
    private val viewModel: UserViewModel,
    private val oldUser: User,
    private val newUser: User
) : Command<User> {
    override fun execute() {
        viewModel.updateUser(newUser)
    }
    
    override fun undo() {
        viewModel.updateUser(oldUser)
    }
}

Q7: MVI 如何处理异步操作的竞态条件?

A:

kotlin
// 问题:快速连续请求可能导致旧请求覆盖新请求

class UserViewModel {
    private var currentRequestId: Int = 0
    
    fun loadUser(userId: Int) {
        val requestId = ++currentRequestId
        
        viewModelScope.launch {
            val user = userRepository.getUser(userId)
            
            // 只处理最新请求
            if (requestId == currentRequestId) {
                _state.update { it.copy(user = user) }
            }
        }
    }
}

// 或者使用 Flow 的 cancel 机制
fun loadUser(userId: Int) {
    job?.cancel()
    job = viewModelScope.launch {
        _state.update { it.copy(isLoading = true) }
        
        try {
            val user = userRepository.getUser(userId)
            _state.update { it.copy(user = user, isLoading = false) }
        } catch (e: Exception) {
            _state.update { it.copy(error = e.message, isLoading = false) }
        }
    }
}

Q8: MVI 的状态如何持久化?

A:

kotlin
// 1. 使用 DataStore
class UserViewModel {
    init {
        restoreState()
    }
    
    private fun restoreState() {
        viewModelScope.launch {
            val saved = dataStore.getState()
            saved?.let { _state.value = it }
        }
    }
    
    override fun onCleared() {
        _state.value?.let { saveState(it) }
        super.onCleared()
    }
}

// 2. 使用 SavedStateHandle(推荐)
class UserViewModel(
    savedStateHandle: SavedStateHandle
) : ViewModel() {
    
    private val userId: String by savedStateHandle.navArgument()
    private val _state = MutableStateFlow(UserState())
    
    // SavedStateHandle 自动处理配置更新
}

6.3 实战问题

Q9: MVI 中如何实现列表下拉刷新和上拉加载更多?

A:

kotlin
data class UserListState(
    val users: List<User> = emptyList(),
    val isLoading: Boolean = false,
    val isLoadingMore: Boolean = false,
    val hasMore: Boolean = true,
    val currentPage: Int = 0,
    val error: String? = null
)

class UserListViewModel @Inject constructor(
    private val userRepository: UserRepository
) : ViewModel() {
    
    private val _state = MutableStateFlow(UserListState())
    val state: StateFlow<UserListState> = _state.asStateFlow()
    
    init {
        loadMore()
    }
    
    fun loadMore() {
        val currentState = _state.value
        
        if (currentState.isLoadingMore || !currentState.hasMore) {
            return
        }
        
        _state.update { it.copy(isLoadingMore = true) }
        
        viewModelScope.launch {
            try {
                val users = userRepository.getUsers(
                    page = currentState.currentPage + 1,
                    size = PAGE_SIZE
                )
                
                _state.update { currentState ->
                    currentState.copy(
                        users = currentState.users + users,
                        isLoadingMore = false,
                        hasMore = users.size >= PAGE_SIZE,
                        currentPage = currentState.currentPage + 1
                    )
                }
            } catch (e: Exception) {
                _state.update { it.copy(isLoadingMore = false, error = e.message) }
            }
        }
    }
    
    fun refresh() {
        _state.update { 
            it.copy(
                users = emptyList(),
                currentPage = 0,
                hasMore = true,
                error = null
            )
        }
        loadMore()
    }
    
    fun clearError() {
        _state.update { it.copy(error = null) }
    }
}

// Compose 实现
@Composable
fun UserListScreen(viewModel: UserListViewModel = hiltViewModel()) {
    val state by viewModel.state.collectAsState()
    
    LazyColumn(
        state = lazyColumnState
    ) {
        items(state.users) { user ->
            UserItem(user)
        }
        
        if (state.isLoadingMore) {
            item {
                CircularProgressIndicator()
            }
        }
        
        if (!state.hasMore) {
            item {
                Text("没有更多了")
            }
        }
    }
    
    // 上拉加载更多
    LaunchedEffect(Unit) {
        lazyColumnState.collect { state ->
            if (state.layoutInfo.visibleItemsInfo.lastOrNull()?.index
                ?: 0 >= state.layoutInfo.totalItemsCount - 2) {
                viewModel.loadMore()
            }
        }
    }
}

Q10: MVI 如何实现搜索功能(防抖、历史记录)?

A:

kotlin
data class SearchState(
    val query: String = "",
    val results: List<SearchResult> = emptyList(),
    val isLoading: Boolean = false,
    val history: List<String> = emptyList(),
    val error: String? = null
)

class SearchViewModel @Inject constructor(
    private val searchRepository: SearchRepository
) : ViewModel() {
    
    private val _state = MutableStateFlow(SearchState())
    val state: StateFlow<SearchState> = _state.asStateFlow()
    
    // 搜索防抖
    private val searchFlow = MutableSharedFlow<String>()
    
    init {
        // 加载历史记录
        loadHistory()
        
        // 监听搜索
        viewModelScope.launch {
            searchFlow
                .debounce(300)
                .distinctUntilChanged()
                .filter { it.isNotBlank() }
                .collect { query ->
                    search(query)
                }
        }
    }
    
    fun onQueryChange(query: String) {
        _state.update { it.copy(query = query) }
        
        if (query.isBlank()) {
            _state.update { it.copy(results = emptyList()) }
            return
        }
        
        viewModelScope.launch {
            searchFlow.emit(query)
        }
    }
    
    private fun search(query: String) {
        _state.update { it.copy(isLoading = true) }
        
        viewModelScope.launch {
            try {
                val results = searchRepository.search(query)
                _state.update { 
                    it.copy(
                        results = results,
                        isLoading = false,
                        history = addHistory(query)
                    )
                }
            } catch (e: Exception) {
                _state.update { it.copy(isLoading = false, error = e.message) }
            }
        }
    }
    
    private fun addHistory(query: String): List<String> {
        val currentState = _state.value.history
        return (listOf(query) + currentState.filter { it != query })
            .take(MAX_HISTORY_SIZE)
    }
    
    fun clearHistory() {
        _state.update { it.copy(history = emptyList()) }
    }
    
    fun searchHistoryItem(query: String) {
        onQueryChange(query)
    }
}

七、MVI 实战案例

7.1 完整的用户列表实现

kotlin
// ==================== 完整的 MVI 用户列表实现 ====================

// 1. State 定义
data class UserListState(
    val users: List<User> = emptyList(),
    val isLoading: Boolean = false,
    val isLoadingMore: Boolean = false,
    val hasMore: Boolean = true,
    val currentPage: Int = 0,
    val error: String? = null,
    val searchQuery: String = "",
    val isSearching: Boolean = false,
    val selectedUserIds: Set<Int> = emptySet()
) {
    fun isSelected(userId: Int): Boolean = selectedUserIds.contains(userId)
    
    fun selectUser(userId: Int): UserListState {
        return copy(selectedUserIds = selectedUserIds + userId)
    }
    
    fun deselectUser(userId: Int): UserListState {
        return copy(selectedUserIds = selectedUserIds - userId)
    }
    
    fun selectAll(): UserListState {
        return copy(selectedUserIds = users.mapTo(mutableSetOf()) { it.id })
    }
    
    fun deselectAll(): UserListState {
        return copy(selectedUserIds = emptySet())
    }
}

// 2. Intent 定义
sealed class UserListIntent {
    object LoadMore : UserListIntent()
    object Refresh : UserListIntent()
    data class Search(val query: String) : UserListIntent()
    data class SelectUser(val userId: Int) : UserListIntent()
    data class DeselectUser(val userId: Int) : UserListIntent()
    object SelectAll : UserListIntent()
    object DeselectAll : UserListIntent()
    data class DeleteSelected(val userIds: Set<Int>) : UserListIntent()
    object ClearError : UserListIntent()
}

// 3. Event 定义
sealed class UserListEvent {
    data class ShowError(val message: String) : UserListEvent()
    data class ShowSuccess(val message: String) : UserListEvent()
    data class NavigateToDetail(val userId: Int) : UserListEvent()
}

// 4. ViewModel 实现
@HiltViewModel
class UserListViewModel @Inject constructor(
    private val userRepository: UserRepository
) : ViewModel() {
    
    companion object {
        private const val PAGE_SIZE = 20
    }
    
    private val _state = MutableStateFlow(UserListState())
    val state: StateFlow<UserListState> = _state.asStateFlow()
    
    private val _event = MutableSharedFlow<UserListEvent>()
    val event: SharedFlow<UserListEvent> = _event.asSharedFlow()
    
    init {
        processIntent(UserListIntent.LoadMore)
    }
    
    fun processIntent(intent: UserListIntent) {
        when (intent) {
            is UserListIntent.LoadMore -> loadMore()
            is UserListIntent.Refresh -> refresh()
            is UserListIntent.Search -> search(intent.query)
            is UserListIntent.SelectUser -> selectUser(intent.userId)
            is UserListIntent.DeselectUser -> deselectUser(intent.userId)
            is UserListIntent.SelectAll -> selectAll()
            is UserListIntent.DeselectAll -> deselectAll()
            is UserListIntent.DeleteSelected -> deleteSelected(intent.userIds)
            is UserListIntent.ClearError -> clearError()
        }
    }
    
    private fun loadMore() {
        val currentState = _state.value
        
        if (currentState.isLoadingMore || !currentState.hasMore) {
            return
        }
        
        _state.update { it.copy(isLoadingMore = true) }
        
        viewModelScope.launch {
            try {
                val users = userRepository.getUsers(
                    page = currentState.currentPage + 1,
                    size = PAGE_SIZE
                )
                
                _state.update { currentState ->
                    currentState.copy(
                        users = currentState.users + users,
                        isLoadingMore = false,
                        hasMore = users.size >= PAGE_SIZE,
                        currentPage = currentState.currentPage + 1
                    )
                }
            } catch (e: Exception) {
                _state.update { it.copy(isLoadingMore = false, error = e.message) }
                _event.emit(UserListEvent.ShowError("加载更多失败"))
            }
        }
    }
    
    private fun refresh() {
        _state.update { 
            it.copy(
                users = emptyList(),
                currentPage = 0,
                hasMore = true,
                error = null
            )
        }
        loadMore()
    }
    
    private fun search(query: String) {
        if (query.isBlank()) {
            _state.update { it.copy(searchQuery = query, results = emptyList()) }
            return
        }
        
        _state.update { it.copy(searchQuery = query, isSearching = true) }
        
        viewModelScope.launch {
            try {
                val users = userRepository.searchUsers(query)
                
                _state.update { currentState ->
                    currentState.copy(
                        users = users,
                        isSearching = false,
                        hasMore = false,
                        currentPage = 0
                    )
                }
            } catch (e: Exception) {
                _state.update { it.copy(isSearching = false, error = e.message) }
                _event.emit(UserListEvent.ShowError("搜索失败"))
            }
        }
    }
    
    private fun selectUser(userId: Int) {
        _state.update { it.selectUser(userId) }
    }
    
    private fun deselectUser(userId: Int) {
        _state.update { it.deselectUser(userId) }
    }
    
    private fun selectAll() {
        _state.update { it.selectAll() }
    }
    
    private fun deselectAll() {
        _state.update { it.deselectAll() }
    }
    
    private fun deleteSelected(userIds: Set<Int>) {
        viewModelScope.launch {
            try {
                userRepository.deleteUsers(userIds)
                
                _state.update { currentState ->
                    currentState.copy(
                        users = currentState.users.filterNot { it.id in userIds },
                        selectedUserIds = emptySet()
                    )
                }
                
                _event.emit(UserListEvent.ShowSuccess("删除成功"))
            } catch (e: Exception) {
                _event.emit(UserListEvent.ShowError("删除失败"))
            }
        }
    }
    
    private fun clearError() {
        _state.update { it.copy(error = null) }
    }
    
    // 点击用户
    fun onUserClick(userId: Int) {
        viewModelScope.launch {
            _event.emit(UserListEvent.NavigateToDetail(userId))
        }
    }
}

// 5. Compose UI 实现
@Composable
fun UserListScreen(
    viewModel: UserListViewModel = hiltViewModel(),
    navigator: Navigator? = null
) {
    val state by viewModel.state.collectAsStateWithLifecycle()
    val snackbarHostState = remember { SnackbarHostState() }
    val scope = rememberCoroutineScope()
    
    // 收集事件
    LaunchedEffect(Unit) {
        viewModel.event.collect { event ->
            when (event) {
                is UserListEvent.ShowError -> {
                    scope.launch {
                        snackbarHostState.showSnackbar(event.message)
                    }
                }
                is UserListEvent.ShowSuccess -> {
                    scope.launch {
                        snackbarHostState.showSnackbar(event.message)
                    }
                }
                is UserListEvent.NavigateToDetail -> {
                    navigator?.navigate("user/${event.userId}")
                }
            }
        }
    }
    
    Scaffold(
        snackbarHost = { SnackbarHost(snackbarHostState) },
        topBar = {
            UserListTopBar(
                state = state,
                onSearch = { query ->
                    viewModel.processIntent(UserListIntent.Search(query))
                },
                onSelectAll = { viewModel.processIntent(UserListIntent.SelectAll) },
                onDeselectAll = { viewModel.processIntent(UserListIntent.DeselectAll) },
                onDeleteSelected = {
                    viewModel.processIntent(
                        UserListIntent.DeleteSelected(state.selectedUserIds)
                    )
                },
                canDelete = state.selectedUserIds.isNotEmpty()
            )
        }
    ) { paddingValues ->
        UserListContent(
            state = state,
            onRefresh = { viewModel.processIntent(UserListIntent.Refresh) },
            onLoadMore = { viewModel.processIntent(UserListIntent.LoadMore) },
            onUserClick = { userId -> viewModel.onUserClick(userId) },
            onUserLongClick = { userId ->
                viewModel.processIntent(
                    if (state.isSelected(userId)) {
                        UserListIntent.DeselectUser(userId)
                    } else {
                        UserListIntent.SelectUser(userId)
                    }
                )
            },
            modifier = Modifier.padding(paddingValues)
        )
    }
}

@Composable
fun UserListContent(
    state: UserListState,
    onRefresh: () -> Unit,
    onLoadMore: () -> Unit,
    onUserClick: (Int) -> Unit,
    onUserLongClick: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
    val refreshState = rememberRefreshState {
        onRefresh()
    }
    
    val lazyColumnState = rememberLazyColumnState()
    
    // 监听滚动到底部
    LaunchedEffect(lazyColumnState) {
        lazyColumnState.collect { state ->
            val visibleItems = state.layoutInfo.visibleItemsInfo
            if (visibleItems.isNotEmpty()) {
                val lastVisibleIndex = visibleItems.last().index
                val totalItems = state.layoutInfo.totalItemsCount
                
                if (lastVisibleIndex >= totalItems - 2) {
                    onLoadMore()
                }
            }
        }
    }
    
    Box {
        if (state.error != null) {
            ErrorView(
                message = state.error,
                onRetry = onRefresh
            )
        }
        
        if (state.users.isEmpty() && !state.isLoading && state.searchQuery.isBlank()) {
            EmptyView(
                message = "暂无用户"
            )
        }
        
        LazyColumn(
            state = lazyColumnState,
            modifier = modifier
        ) {
            items(
                items = state.users,
                key = { user -> user.id }
            ) { user ->
                UserListItem(
                    user = user,
                    isSelected = state.isSelected(user.id),
                    onClick = { onUserClick(user.id) },
                    onLongClick = { onUserLongClick(user.id) }
                )
            }
            
            if (state.isLoadingMore) {
                item {
                    LoadingItem()
                }
            }
            
            if (!state.hasMore && state.users.isNotEmpty()) {
                item {
                    NoMoreItem()
                }
            }
        }
    }
}

八、MVI 面试速查表

问题难度关键点
MVI 核心概念⭐⭐单向数据流、不可变 State
State 不可变性⭐⭐⭐线程安全、状态追溯
Event 处理方式⭐⭐⭐SharedFlow/SingleEvent
MVI vs MVVM⭐⭐⭐数据流、状态管理
防抖实现⭐⭐⭐debounce、distinctUntilChanged
竞态条件处理⭐⭐⭐⭐请求 ID、cancel 机制
状态持久化⭐⭐⭐SavedStateHandle、DataStore
撤销重做⭐⭐⭐⭐命令模式
列表优化⭐⭐⭐key、diff、分页
性能优化⭐⭐⭐⭐@Stable、批量更新

九、参考资料

9.1 官方文档

9.2 推荐资源

9.3 相关模式

  • [MVVM 架构](./01_MVVM 架构与 Hilt 依赖注入.md)
  • [MVP 架构](./02_MVP 架构.md)

🔗 上一篇: [MVP 架构](./02_MVP 架构.md)

🔗 下一篇: [VIPER 架构](./04_VIPER 架构.md)