mcpField
A
android-viewmodel

android-viewmodel

github.com/new-silvermoon开发992552 次安装13 周前更新
代码仓库

关于 android-viewmodel

面向 Android Kotlin 开发者的 ViewModel 与状态管理指南,涵盖 StateFlow、SharedFlow 及生命周期感知的收集方式,并强调不可变 UI 状态和显式 backing fields。

主要功能

  1. 01用 StateFlow 表示持久 UI 状态
  2. 02用 SharedFlow 发送一次性事件
  3. 03生命周期感知地收集流
  4. 04在 viewModelScope 中启动任务
  5. 05优先使用显式 backing fields

适用场景

  1. 01设计单一屏幕的不可变状态
  2. 02处理导航和 Snackbar 等 UI 事件
  3. 03在 Compose 中收集状态与事件
  4. 04在 View 系统中按生命周期收集流

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/new-silvermoon/awesome-android-agent-skills --skill android-viewmodel

安装后在 Claude Code 中输入/android-viewmodel确认已加载。

详细介绍

Android ViewModel 和状态管理

说明

使用 ViewModel 管理 UI 状态和业务逻辑。它应在配置变更后继续存在,并公开不可变 API。

Kotlin 2.3+

优先使用显式后备字段,而不是传统的 _state/state 后备属性模式。

  • Kotlin 2.3:实验性功能(-Xexplicit-backing-fields)
  • Kotlin 2.4+:稳定(推荐)

除非目标是较旧的 Kotlin 版本,否则避免创建重复的后备属性。


1. UI 状态(StateFlow)

用途

表示持久的 UI 状态。

示例:

  • 加载中
  • 成功(data)
  • 错误(message)
  • 空
声明

使用显式后备字段。

val uiState: StateFlow<UiState>
    field = MutableStateFlow(UiState.Loading)

后备字段是可变的(MutableStateFlow),而使用方只能看到 StateFlow。

更新状态

优先使用 update { } 进行原子更新。

_uiState.update { old ->
    old.copy(isLoading = false)
}

使用显式后备字段时:

uiState.update {
    it.copy(isLoading = false)
}

在适当的情况下,也可以直接赋值。

uiState.value = UiState.Success(data)

2. 单次事件(SharedFlow)

用途

用于不会在重组或配置变更后保留的临时事件。

示例:

  • 导航
  • 显示 Snackbar
  • 显示 Toast
  • 打开对话框
  • 请求权限
声明
val uiEvent: SharedFlow<UiEvent>
    field = MutableSharedFlow(
        replay = 0,
        extraBufferCapacity = 1
    )

replay = 0 可确保重新创建后不会重放事件。

对于使用 tryEmit() 发送的 UI 事件,建议设置 extraBufferCapacity = 1。

发送事件

挂起:

uiEvent.emit(UiEvent.NavigateBack)

非挂起:

uiEvent.tryEmit(UiEvent.ShowSnackbar("Saved"))

3. 在 Compose 中收集

StateFlow

始终以感知生命周期的方式收集。

val state by viewModel.uiState.collectAsStateWithLifecycle()
SharedFlow

在单个 LaunchedEffect 中收集。

LaunchedEffect(Unit) {
    viewModel.uiEvent.collect { event ->
        when (event) {
            is UiEvent.NavigateBack -> { /* ... */ }
            is UiEvent.ShowSnackbar -> { /* ... */ }
        }
    }
}

不要使用 collectAsState() 收集 SharedFlow。


4. 在 XML/View 系统中收集

使用感知生命周期的收集方式。

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.uiState.collect {
            render(it)
        }
    }
}

事件也同样如此。

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.uiEvent.collect(::handleEvent)
    }
}

5. 协程作用域

在 viewModelScope 中启动所有 ViewModel 工作。

viewModelScope.launch {
    repository.refresh()
}

业务逻辑最好委托给 UseCase 或 Repository。


6. 状态设计

使用单个不可变状态对象表示整个屏幕。

data class UiState(
    val isLoading: Boolean = false,
    val items: List<Item> = emptyList(),
    val error: String? = null
)

除非多个 StateFlow 的生命周期确实不同,否则避免为同一个屏幕公开多个互不相关的 StateFlow。


7. 最佳实践

公开不可变 API(StateFlow、SharedFlow)

优先使用显式后备字段(Kotlin 2.4+)

使用不可变 UI 状态

修改状态时使用 update {}

使用 SharedFlow 保存单次事件

不要在 Composable 中编写业务逻辑

使用 collectAsStateWithLifecycle()

在 View 中使用 repeatOnLifecycle()

在 viewModelScope 中启动工作

不要公开 MutableStateFlow

不要使用 StateFlow 处理导航事件

不要使用 SharedFlow 保存持久的屏幕状态

显式后备字段可用时,不要保留重复的 _state 属性

评论

支持 Markdown