四、ViewModel + StateFlow + 状态持久化

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

UI与数据处理逻辑分离

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

1、为什么需要ViewModel

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

2、StateFlow是什么

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

  • 始终持有最新状态值

  • 支持多个观察者

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

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

    MutableStateFlow → 内部修改

    StateFlow → 外部只读(封装性)

3、collectAsState():链接Flow与Compose

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

4、项目完整结构重构

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

    • 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状态数据类

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

    • 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管理状态

    • 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()
                          )
                      }
                  }
              }
          }
      }
      
      
  • 启动查看结果
    修改后运行效果

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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐