四、ViewModel + StateFlow + 状态持久化

四、ViewModel + StateFlow + 状态持久化

将状态从Composable组件中分离出来,单独处理数据。

UI与数据处理逻辑分离

今天的内容比较少(主要是今天有点忙 -。-!)

1、为什么需要ViewModel

  • ViewModel生命周期创建于Activity启动,销毁与Activity真正的finish,不会因为中途状态改变而被销毁重建(例如屏幕旋转,切换页面等)
  • 状态分散在多个Composable中,难以复用测试。
  • 无法在多个Composable中共享状态。
  • 要做到数据与UI的分离,让逻辑更加清晰,代码结构更加健壮。

2、StateFlow是什么

  • 是Kotlin协程中的热流(Hot Flow)

  • 始终持有最新状态值

  • 支持多个观察者

  • 完美适配Compose的状态驱动UI

    KOTLIN 复制代码
    //用于ViewModel内部更新数据
    private val _uiState = MutableStateFlow(NoteUIState(isLoading = true))
    // 只读,暴露给UI读取
    val uiState: StateFlow<NoteUiState> = _uiState.asStateFlow() 

    MutableStateFlow → 内部修改

    StateFlow → 外部只读(封装性)

3、collectAsState():链接Flow与Compose

kotlin 复制代码
val uiState by viewModel.uiState.collectAsState()
  • 自动在Composable进入时收集(collect)Flow
  • 自动在退出时取消收集
  • 当Flow发出新值时 -> 触发重组(Recomposition)

4、项目完整结构重构

  • 相关依赖包(第一篇中已经全部导入)

    kotlin 复制代码
    dependencies {
        implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7")
        implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7")
        implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
        implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0")
    }
  • 定义UI状态数据类

    kotlin 复制代码
    data class NoteUIState(
        val notes:List<Note> = emptyList(),
        val isLoading: Boolean = false,
        val error:String? = null
    )
  • 创建NoteViewModel类

    kotlin 复制代码
    package com.jiahengfei.ktdm
    
    import androidx.lifecycle.ViewModel
    import androidx.lifecycle.viewModelScope
    import kotlinx.coroutines.delay
    import kotlinx.coroutines.flow.MutableStateFlow
    import kotlinx.coroutines.flow.StateFlow
    import kotlinx.coroutines.flow.asStateFlow
    import kotlinx.coroutines.launch
    
    /**
     * Create by zFox from AndroidStudio2022.01
     * 日期:2026/1/8 20:01
     * 描述:ViewModel数据模型类
     */
    class NoteViewModel: ViewModel() {
        private val _uiState = MutableStateFlow(NoteUIState(isLoading = true))
        val uiState: StateFlow<NoteUIState> = _uiState.asStateFlow()
    
        init {
            loadNotes()
        }
    
        private fun loadNotes() {
            viewModelScope.launch {
                delay(2000)
                val mockNotes = listOf(
                    Note(1, "协程进阶", "学会了 ViewModel + StateFlow", "2026-01-08", listOf("Compose")),
                    Note(2, "状态管理", "UI 状态现在由 ViewModel 驱动", "2026-01-08", listOf("MVVM")),
                    Note(3, "协程进阶2", "学会了 ViewModel + StateFlow", "2026-01-08", listOf("Compose")),
                    Note(4, "状态管理2", "UI 状态现在由 ViewModel 驱动", "2026-01-08", listOf("MVVM"))
                )
    
                _uiState.value = NoteUIState(
                    notes = mockNotes,
                    isLoading = false
                )
            }
        }
    }
  • 更新笔记列表NoteListScreen,使用ViewModel管理状态

    kotlin 复制代码
    package com.jiahengfei.ktdm
    
    import androidx.compose.foundation.layout.Arrangement
    import androidx.compose.foundation.layout.Box
    import androidx.compose.foundation.layout.PaddingValues
    import androidx.compose.foundation.layout.fillMaxSize
    import androidx.compose.foundation.lazy.LazyColumn
    import androidx.compose.foundation.lazy.items
    import androidx.compose.material3.CircularProgressIndicator
    import androidx.compose.runtime.Composable
    import androidx.compose.runtime.collectAsState
    import androidx.compose.runtime.getValue
    import androidx.compose.ui.Alignment
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.unit.dp
    import androidx.lifecycle.viewmodel.compose.viewModel
    import kotlinx.coroutines.Dispatchers
    import kotlinx.coroutines.delay
    import kotlinx.coroutines.withContext
    
    /**
     * Create by zFox from AndroidStudio2022.01
     * 日期:2026/1/7 19:10
     * 描述:笔记本列表
     */
    
    /**
     * 挂起函数
     */
    suspend fun requestNotes(){
        return withContext(Dispatchers.IO){
            delay(2000)
        }
    }
    
    @Composable
    fun NoteListScreen(
        modifier: Modifier = Modifier,
        showSnackBar: (String) -> Unit,
        viewModel: NoteViewModel = viewModel()
    ) {
        val uiState by viewModel.uiState.collectAsState()
    
        Box(
            modifier = modifier.fillMaxSize(),
            contentAlignment = Alignment.Center
        ) {
            if (uiState.isLoading) {
                CircularProgressIndicator()
            } else {
                //显示笔记本列表
                LazyColumn(
                    modifier = Modifier.fillMaxSize(),
                    verticalArrangement = Arrangement.spacedBy(8.dp),
                    contentPadding = PaddingValues(16.dp)
                ) {
                    items(uiState.notes) { note ->
                        NoteCard(
                            note = note,
                            showSnackBar,
                            modifier = Modifier.fillParentMaxWidth()
                        )
                    }
                }
            }
        }
    }
  • 启动查看结果

这就是第四天的全部内容啦,明天我们将切换到实战项目进行编写边学。

相关推荐
胡致和17 小时前
配置变更后,弹窗为什么飞到了最左边?
kotlin
zhangphil1 天前
Android Page 3 Flow读sql数据库媒体文件,Kotlin
android·kotlin
小书房1 天前
Kotlin使用体验及理解1
android·开发语言·kotlin
Kapaseker1 天前
我想让同事知道我很懂 Compose 怎么办?
android·kotlin
jinanwuhuaguo2 天前
OpenClaw工程解剖——RAG、向量织构与“记忆宫殿”的索引拓扑学(第十三篇)
android·开发语言·人工智能·kotlin·拓扑学·openclaw
jinanwuhuaguo2 天前
OpenClaw协议霸权——从 MCP 标准到意图封建化的政治经济学(第十八篇)
android·人工智能·kotlin·拓扑学·openclaw
zhangphil2 天前
Android sql查媒体数据封装room Dao构造AndroidViewModel,RecyclerView宫格展示,Kotlin
android·kotlin
jinanwuhuaguo2 天前
反熵共同体——OpenClaw的宇宙热力学本体论(第十七篇)
大数据·人工智能·安全·架构·kotlin·openclaw
pengyu2 天前
【Kotlin 协程修仙录 · 筑基境 · 中阶】 | 身份证与通行证:CoroutineContext 的深度解剖
android·kotlin
夏沫琅琊2 天前
android 短信读取与导出技术
android·kotlin