/ Android  Jetpack Compose  状态管理  remember  ViewModel  Kotlin  UI开发  教程 

Android Jetpack Compose 状态管理实战:从 remember 到 ViewModel 完整教程


文章封面

Jetpack Compose 已经成为 Android 官方推荐的 UI 框架,但很多开发者在刚上手时都会对它的状态管理感到困惑:remembermutableStateOfrememberSaveableViewModel……这些到底该怎么选?本文从最基础的概念出发,配合真实代码示例,带你彻底搞清楚 Compose 中的状态管理体系。

一、为什么 Compose 需要特殊的状态管理?

传统 View 系统中,视图有自己的状态(比如 EditText 记住用户输入)。但 Compose 的核心思想是单向数据流——UI 是状态的函数,状态变化触发重组(Recomposition)。这意味着:

  • 普通变量在重组时会被重新初始化,无法保持状态

  • 必须用 Compose 提供的状态容器才能让 UI 响应数据变化

  • 状态的生命周期需要和 Composable 的生命周期匹配

简单来说:不用 remember,你的变量会在每次重组时归零。

// ❌ 错误写法:count 每次重组都会被重置为 0
@Composable
fun BrokenCounter() {
    var count = 0
    Button(onClick = { count++ }) {
        Text("点击次数: $count")
    }
}

// ✅ 正确写法:用 remember + mutableStateOf
@Composable
fun WorkingCounter() {
    var count by remember { mutableStateOf(0) }
    Button(onClick = { count++ }) {
        Text("点击次数: $count")
    }
}

二、remember 和 mutableStateOf 的本质

remember 的作用是在重组间保存一个值,而 mutableStateOf 创建了一个可观察的状态对象——当它的值改变时,Compose 会自动触发依赖它的 Composable 重新组合。

两者通常配合使用,但要注意它们是独立的:

// mutableStateOf 不用 remember:每次重组都创建新的 State 对象(状态丢失)
var state = mutableStateOf(0)  // ❌

// remember 不用 mutableStateOf:值被记住了,但改变它不触发重组
var count by remember { mutableStateOf(0) }  // ✅

// 等价写法(by 是 Kotlin 委托属性语法糖)
val countState = remember { mutableStateOf(0) }
countState.value++  // 直接用 .value

踩坑记录: 很多新手会忘记 remember 只在当前 Composable 的生命周期内有效。当 Composable 从组合树中移除再重新添加时(比如切换 Tab),remember 的值会丢失。

三、rememberSaveable:跨越配置变更保存状态

当屏幕旋转或系统资源回收时,remember 的值会丢失。rememberSaveable 能将状态自动保存到 Bundle,行为类似 onSaveInstanceState

@Composable
fun SearchBar() {
    // 旋转屏幕后输入内容不会丢失
    var searchText by rememberSaveable { mutableStateOf("") }
    
    TextField(
        value = searchText,
        onValueChange = { searchText = it },
        label = { Text("搜索") }
    )
}

对于无法自动序列化的自定义类型,需要实现 Saver

data class City(val name: String, val code: String)

val CitySaver = run {
    val nameKey = "name"
    val codeKey = "code"
    mapSaver(
        save = { mapOf(nameKey to it.name, codeKey to it.code) },
        restore = { City(it[nameKey] as String, it[codeKey] as String) }
    )
}

@Composable
fun CitySelector() {
    var selectedCity by rememberSaveable(stateSaver = CitySaver) {
        mutableStateOf(City("北京", "BJ"))
    }
    // ...
}

经验建议:remember 还是 rememberSaveable?如果这个状态丢失了用户会不高兴(比如填写了一半的表单),用 rememberSaveable;如果只是 UI 临时状态(比如下拉菜单展开收起),用 remember 就够了。

四、状态提升:让组件更可复用

一个优秀的 Composable 应该是"无状态"的——把状态提升到调用者,让组件只负责展示。这就是 Compose 官方推荐的状态提升(State Hoisting)模式。

// ❌ 有状态的组件(难以复用,无法测试)
@Composable
fun StatefulTextField() {
    var text by remember { mutableStateOf("") }
    TextField(value = text, onValueChange = { text = it })
}

// ✅ 无状态的组件(状态提升)
@Composable
fun StatelessTextField(
    text: String,
    onTextChange: (String) -> Unit
) {
    TextField(value = text, onValueChange = onTextChange)
}

// 调用时在外部持有状态
@Composable
fun ParentComposable() {
    var text by remember { mutableStateOf("") }
    StatelessTextField(
        text = text,
        onTextChange = { text = it }
    )
}

状态提升的核心原则:状态应该被提升到所有需要它的 Composable 的最低公共祖先

五、ViewModel 集成:管理界面级状态

对于跨越多个 Composable 的复杂状态,或者需要在后台执行业务逻辑的场景,应该使用 ViewModel。Compose 提供了 viewModel() 函数来获取 ViewModel 实例:

// 添加依赖(build.gradle.kts)
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.0")

// ViewModel 定义
class UserViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(UserUiState())
    val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
    
    fun loadUser(userId: String) {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true) }
            try {
                val user = userRepository.getUser(userId)
                _uiState.update { it.copy(user = user, isLoading = false) }
            } catch (e: Exception) {
                _uiState.update { it.copy(error = e.message, isLoading = false) }
            }
        }
    }
    
    fun updateName(name: String) {
        _uiState.update { it.copy(user = it.user?.copy(name = name)) }
    }
}

data class UserUiState(
    val user: User? = null,
    val isLoading: Boolean = false,
    val error: String? = null
)

// 在 Composable 中使用
@Composable
fun UserScreen(
    userId: String,
    viewModel: UserViewModel = viewModel()
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    
    LaunchedEffect(userId) {
        viewModel.loadUser(userId)
    }
    
    when {
        uiState.isLoading -> CircularProgressIndicator()
        uiState.error != null -> Text("错误:${uiState.error}")
        uiState.user != null -> UserContent(
            user = uiState.user!!,
            onNameChange = viewModel::updateName
        )
    }
}

注意: 使用 collectAsStateWithLifecycle() 而非 collectAsState(),前者会在 Composable 不可见时自动暂停收集,更省资源。

六、选择指南:用哪个?

总结一下不同状态管理方案的适用场景:

  • remember + mutableStateOf:单个 Composable 内部的临时 UI 状态(动画、展开收起等)

  • rememberSaveable:需要在配置变更后恢复的 UI 状态(表单输入、列表滚动位置)

  • 状态提升:多个 Composable 共享的状态,提升到最近公共父节点

  • ViewModel + StateFlow:屏幕级状态、需要业务逻辑处理的数据、跨越导航的状态

  • DataStore / 数据库:需要持久化的用户设置或应用数据

掌握这套体系后,你会发现 Compose 的状态管理其实非常清晰。从 remember 起步,根据需求逐层上升到 ViewModel,每一层都有明确的职责边界。希望这篇文章能帮你在实际项目中做出正确的技术选型。

发布评论

热门评论区: