Jetpack Compose State<T> 和 MutableState<T> 是由 Compose 快照系统支持的通用值存储器。虽然快照系统最常用于驱动界面重组,但其本身并不与界面渲染绑定。
借助 AppState 库(Transform 和 StateStore),您可以在 ViewModel、管理器、代码库和后台服务之间保留、派生、观察和持久保存 Compose State。
为何在界面之外使用 Compose State?
当您在状态观察代码块(例如界面可组合项、transform 或 derivedStateOf)中读取 Compose State 时,Compose 运行时会在架构的任何层中提供两大优势:
- 自动和动态依赖项跟踪:读取状态的
.value(或使用by属性委托)会自动将该状态注册为代码块的依赖项。由于跟踪是动态的,因此依赖项是在运行时由执行的代码路径决定的。如果未采用条件分支(例如,在if检查或提前return之后),则不会读取该分支内的状态,并且对这些状态的更改不会触发不必要的重新计算。 - 一致性和细化程度:您可以声明单独的细粒度
MutableState属性,并将多属性写入操作封装在Snapshot.withMutableSnapshot中。中间更新会隔离到可变快照,并在代码块完成时以原子方式应用,因此在快照内读取的观察器永远不会看到不一致的中间状态,并且仅在它们读取的特定属性发生更改时才会重新计算。与StateFlow不同,您不必在单个单体式StateFlow<UiState>(当任何字段发生更改时,都会使每个收集器失效)和单独的StateFlow属性(无法在单个原子事务中一起更新)之间做出选择。
在整个架构中选择合适的状态 API
根据状态应保留的时长以及需要访问该状态的组件,选择如何保留和持久保存 Compose State:
| 州类别 | 生命周期和范围 | 示例 | 推荐的 Compose State API |
|---|---|---|---|
| 有效状态 | 始终存储在内存中;在启动时重新查询,不会在进程终止后保留。 | 网络连接状态、蓝牙连接状态、临时界面状态(例如内容选择)。 |
|
| 瞬态界面状态 | 在配置更改和系统发起的进程终止后继续存在,但不会无限期存储;作用域限定为单个屏幕或 activity。 | 展开或收起的界面元素、滚动位置、屏幕搜索查询或过滤条件切换开关。 |
|
| 瞬态会话状态 | 在后台进程终止后继续存在,但不会无限期存储;可在 activity、服务或 widget 之间共享。 | 正在运行的专注计时器、多步骤结账购物车或正在填写的表单草稿。 |
|
| 永久状态 | 无限期保留在磁盘上。 | 用户偏好设置和设置 (DataStore)、结构化应用数据和媒体库 (Room)。 | 使用 collectAsState 在 transform 内收集的 DataStore 或 Room flow |
在 ViewModel 中使用 Compose 状态
在 ViewModel 中,您可以将独立的屏幕输入声明为精细的 MutableState 属性,使用 SavedStateHandle.saveable 在进程终止后持久保存这些属性,使用 Snapshot.withMutableSnapshot 以原子方式更新多个属性,并在 transform 中将这些属性与代码库 Flow 流组合在一起:
sealed interface TaskListState { data object Loading : TaskListState data class Success( val tasks: List<Task>, val selectedTaskIds: Set<String>, val isOnline: Boolean = true, ) : TaskListState } @HiltViewModel class TaskListViewModel @Inject constructor( private val tasksRepository: TasksRepository, savedStateHandle: SavedStateHandle, ) : ViewModel() { var selectedTaskIds by mutableStateOf(emptySet<String>()) private set var searchQuery by savedStateHandle.saveable { mutableStateOf("") } private set fun updateSearchQuery(query: String) { Snapshot.withMutableSnapshot { searchQuery = query selectedTaskIds = emptySet() } } val tasksState: State<TaskListState> = transform<TaskListState>( initialValue = TaskListState.Loading, scope = viewModelScope, ) { val tasks = remember(searchQuery) { tasksRepository.getTasks(searchQuery) }.collectAsState(initial = null).value ?: return@transform TaskListState.Loading TaskListState.Success( tasks = tasks, selectedTaskIds = selectedTaskIds, ) } }
此 ViewModel 中的关键模式:
- 使用
SavedStateHandle.saveable实现响应式保存状态:使用mutableStateOf(来自androidx.lifecycle:lifecycle-viewmodel-compose)将searchQuery委托给SavedStateHandle.saveable,可在进程终止后将值保留为MutableState,因此transform会自动跟踪读取。避免使用裸SavedStateHandle.saved,它会返回一个普通的 Kotlin 属性,而不是 ComposeState,并且在更改时不会触发重组。 - 使用
Snapshot.withMutableSnapshot进行原子多属性写入:在Snapshot.withMutableSnapshot内更新searchQuery和selectedTaskIds会隔离写入,并在单个原子快照事务中提交这两项更改,因此观察器(包括transform)会在一次重组传递中同时看到这两项更新,而不是观察到新searchQuery与过时selectedTaskIds配对。 - 使用
remember(key)跨重组重复使用 flow:将tasksRepository.getTasks(searchQuery)封装在remember中,并使用searchQuery作为键,可确保当selectedTaskIds发生更改并重组transform代码块时,Compose 会重复使用现有数据库Flow,而不是重新启动查询。
在整个应用中共享实时系统状态
对于在多个 ViewModel、activity 或服务之间共享的实时系统状态(例如网络或蓝牙连接状态),您不希望在进程终止后将过时状态保留到磁盘。而应在启动时查询当前系统状态,将其保存在应用级单例内的私有 mutableStateOf 属性中,并公开只读 State<T>:
interface NetworkMonitor { val isOnline: State<Boolean> } @Singleton class LiveNetworkMonitor @Inject constructor( @ApplicationContext context: Context, ) : NetworkMonitor { private val connectivityManager = context.getSystemService(ConnectivityManager::class.java) private val _isOnline = mutableStateOf(checkCurrentConnectivity()) override val isOnline: State<Boolean> = _isOnline private val callback = object : ConnectivityManager.NetworkCallback() { override fun onCapabilitiesChanged( network: Network, networkCapabilities: NetworkCapabilities, ) { _isOnline.value = networkCapabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_INTERNET ) } override fun onLost(network: Network) { _isOnline.value = false } } init { connectivityManager.registerDefaultNetworkCallback(callback) } private fun checkCurrentConnectivity(): Boolean { val network = connectivityManager.activeNetwork ?: return false val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false return capabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_INTERNET ) } }
MutableState 支持从任何线程进行一致的读取和更新,因此 NetworkCallback 可以直接从其后台回调线程更新 _isOnline.value。如果后台回调同时更新多个 MutableState 属性,请将写入操作封装在 Snapshot.withMutableSnapshot 中,以便以原子方式应用这些操作。
应用中的任何组件都可以读取 networkMonitor.isOnline.value,无论是一次性检查、在界面可组合项内,还是在状态观察块(例如 listener、derivedStateOf 或 transform)内。例如,TaskListViewModel 可以在其 transform 块内读取 networkMonitor.isOnline.value:
val tasksState: State<TaskListState> = transform<TaskListState>( initialValue = TaskListState.Loading, scope = viewModelScope, ) { val tasks = remember(searchQuery) { tasksRepository.getTasks(searchQuery) }.collectAsState(initial = null).value ?: return@transform TaskListState.Loading TaskListState.Success( tasks = tasks, selectedTaskIds = selectedTaskIds, isOnline = networkMonitor.isOnline.value, ) }
由于 transform 会跟踪 networkMonitor.isOnline.value 以及 searchQuery、selectedTaskIds 和代码库 Flow,因此连接性的任何更改都会自动重新计算 TaskListState.Success,而无需重新查询数据库。