本章深入 Android 现代状态管理体系:ViewModel 生命周期原理、LiveData 与 Flow/StateFlow/SharedFlow 的选择策略、Compose 中的单向数据流架构,以及复杂 UI 状态建模。
📋 章节目录
| 节 | 主题 |
|---|---|
| 3.1 | ViewModel 原理与使用 |
| 3.2 | LiveData 与 StateFlow / SharedFlow |
| 3.3 | Compose 中的状态提升与单向数据流 |
| 3.4 | UDF(Unidirectional Data Flow)架构模式 |
| 3.5 | 复杂 UI 状态建模(UiState sealed class) |
| 3.6 | SavedStateHandle 与进程恢复 |
3.1 ViewModel 原理与使用
ViewModel 的生命周期
Activity.onCreate() → ViewModel 创建(或复用)
Activity.onDestroy()(横竖屏旋转)→ ViewModel 保留!
Activity.onDestroy()(用户按返回键)→ ViewModel 销毁,onCleared() 调用
原理: ViewModel 存储在 ViewModelStore 中,而 ViewModelStore 在配置变更时通过 NonConfigurationInstance 机制保留。
kotlin
// 基础 ViewModel
class UserViewModel(
private val userRepository: UserRepository,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// 从 SavedStateHandle 恢复状态
private val userId: Int = savedStateHandle["user_id"] ?: -1
// StateFlow:替代 LiveData(Kotlin 首选)
private val _uiState = MutableStateFlow<UserUiState>(UserUiState.Loading)
val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
// SharedFlow:用于一次性事件(导航、Toast 等)
private val _events = MutableSharedFlow<UserEvent>()
val events: SharedFlow<UserEvent> = _events.asSharedFlow()
init {
loadUser()
}
fun loadUser() {
viewModelScope.launch {
_uiState.value = UserUiState.Loading
userRepository.getUser(userId)
.onSuccess { user ->
_uiState.value = UserUiState.Success(user)
}
.onFailure { error ->
_uiState.value = UserUiState.Error(error.message ?: "未知错误")
}
}
}
fun onFollowClick(userId: Int) {
viewModelScope.launch {
userRepository.followUser(userId)
.onSuccess {
_events.emit(UserEvent.ShowToast("关注成功"))
// 更新当前状态中的关注状态
updateFollowState(userId, true)
}
.onFailure {
_events.emit(UserEvent.ShowToast("关注失败,请重试"))
}
}
}
private fun updateFollowState(userId: Int, isFollowing: Boolean) {
val current = _uiState.value
if (current is UserUiState.Success) {
_uiState.value = current.copy(
user = current.user.copy(isFollowing = isFollowing)
)
}
}
override fun onCleared() {
super.onCleared()
// 清理资源(通常不需要手动,协程会自动取消)
}
}
// ViewModel 工厂(使用 Hilt 时自动处理)
class UserViewModelFactory(
private val repository: UserRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(UserViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return UserViewModel(repository, SavedStateHandle()) as T
}
throw IllegalArgumentException("Unknown ViewModel: $modelClass")
}
}
ViewModel 在不同场景中的获取
kotlin
// Activity 中获取
class UserActivity : AppCompatActivity() {
// 委托属性:by viewModels()
private val viewModel: UserViewModel by viewModels()
// 带参数(使用 Hilt 时不需要手动 Factory)
private val viewModelWithArgs: UserViewModel by viewModels {
UserViewModelFactory(UserRepositoryImpl())
}
}
// Fragment 中共享 Activity 级别 ViewModel
class UserDetailFragment : Fragment() {
// activityViewModels:获取 Activity 共享的 ViewModel
private val sharedViewModel: UserViewModel by activityViewModels()
// viewModels:Fragment 私有 ViewModel
private val localViewModel: DetailViewModel by viewModels()
}
// Compose 中获取
@Composable
fun UserScreen(
viewModel: UserViewModel = hiltViewModel() // Hilt 注入
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// ...
}
3.2 LiveData 与 StateFlow / SharedFlow
LiveData vs StateFlow 对比
| 特性 | LiveData | StateFlow |
|---|---|---|
| 语言 | Java/Kotlin | Kotlin 协程 |
| 生命周期感知 | 自动(observe 绑定) | 需手动(collectAsState / repeatOnLifecycle) |
| 初始值 | 可无 | 必须有 |
| 操作符 | 有限(map、switchMap) | 丰富(Flow 全套) |
| 线程安全 | postValue(后台) | 协程上下文 |
| 推荐场景 | 传统 View + 向后兼容 | Compose / 协程优先 |
kotlin
// StateFlow:热流,有状态,适合 UI 状态
class ProductViewModel : ViewModel() {
// 搜索关键词(双向绑定)
private val _searchQuery = MutableStateFlow("")
val searchQuery: StateFlow<String> = _searchQuery.asStateFlow()
// 产品列表:基于搜索关键词的派生状态
val products: StateFlow<List<Product>> = _searchQuery
.debounce(300L) // 防抖
.flatMapLatest { query ->
if (query.isBlank()) productRepository.getAllProducts()
else productRepository.searchProducts(query)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000L), // 5秒无订阅者停止
initialValue = emptyList()
)
fun onSearchQueryChange(query: String) {
_searchQuery.value = query
}
}
// SharedFlow:热流,无初始状态,适合事件
class CheckoutViewModel : ViewModel() {
// 一次性事件(导航、弹窗等)
private val _sideEffects = MutableSharedFlow<CheckoutSideEffect>()
val sideEffects: SharedFlow<CheckoutSideEffect> = _sideEffects.asSharedFlow()
// replay=0:不重放(事件只发送一次)
// 如果需要重放(如错误信息)可设置 replay=1
fun checkout() {
viewModelScope.launch {
try {
orderRepository.placeOrder()
_sideEffects.emit(CheckoutSideEffect.NavigateToSuccess)
} catch (e: InsufficientStockException) {
_sideEffects.emit(CheckoutSideEffect.ShowDialog("库存不足"))
} catch (e: PaymentException) {
_sideEffects.emit(CheckoutSideEffect.ShowSnackbar("支付失败:${e.message}"))
}
}
}
}
sealed class CheckoutSideEffect {
object NavigateToSuccess : CheckoutSideEffect()
data class ShowDialog(val message: String) : CheckoutSideEffect()
data class ShowSnackbar(val message: String) : CheckoutSideEffect()
}
Flow 操作符实战
kotlin
class OrderViewModel(
private val orderRepository: OrderRepository
) : ViewModel() {
// 组合多个流
val dashboardData: StateFlow<DashboardData> = combine(
orderRepository.getPendingOrders(),
orderRepository.getCompletedOrdersCount(),
userRepository.getCurrentUser()
) { pendingOrders, completedCount, user ->
DashboardData(
pendingOrders = pendingOrders,
completedCount = completedCount,
userName = user.name
)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), DashboardData())
// 错误处理与重试
val orderList: StateFlow<List<Order>> = orderRepository
.getOrders()
.retry(3) { cause ->
cause is NetworkException // 只重试网络错误
}
.catch { e ->
emit(emptyList()) // 最终失败时降级
_events.emit(Event.ShowError(e.message ?: "加载失败"))
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
private val _events = MutableSharedFlow<Event>()
val events = _events.asSharedFlow()
// 使用 callbackFlow 包装回调 API
private fun listenToRealTimeOrders(): Flow<List<Order>> = callbackFlow {
val listener = object : OrderListener {
override fun onOrdersChanged(orders: List<Order>) {
trySend(orders) // 线程安全发送
}
override fun onError(e: Exception) {
close(e) // 关闭流并传递错误
}
}
val subscription = realTimeDatabase.addListener(listener)
awaitClose {
realTimeDatabase.removeListener(subscription) // 清理资源
}
}
}
3.3 Compose 中的状态提升与单向数据流
collectAsStateWithLifecycle(推荐方式)
kotlin
@Composable
fun ProductListScreen(
viewModel: ProductViewModel = hiltViewModel()
) {
// ✅ collectAsStateWithLifecycle:App 进入后台时自动停止收集
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val products by viewModel.products.collectAsStateWithLifecycle()
// 处理一次性事件
val lifecycleOwner = LocalLifecycleOwner.current
LaunchedEffect(viewModel.events) {
viewModel.events.collect { event ->
when (event) {
is Event.ShowToast -> { /* show toast */ }
is Event.Navigate -> { /* navigate */ }
}
}
}
ProductListContent(
uiState = uiState,
products = products,
onRefresh = viewModel::refresh,
onProductClick = viewModel::onProductClick,
onSearchQueryChange = viewModel::onSearchQueryChange
)
}
// ✅ 无状态 Composable(方便测试和预览)
@Composable
private fun ProductListContent(
uiState: ProductUiState,
products: List<Product>,
onRefresh: () -> Unit,
onProductClick: (Product) -> Unit,
onSearchQueryChange: (String) -> Unit
) {
when (uiState) {
ProductUiState.Loading -> LoadingScreen()
is ProductUiState.Error -> ErrorScreen(
message = uiState.message,
onRetry = onRefresh
)
is ProductUiState.Success -> {
LazyColumn {
items(products, key = { it.id }) { product ->
ProductCard(product = product, onClick = { onProductClick(product) })
}
}
}
}
}
// Preview(无需 ViewModel,直接传参)
@Preview
@Composable
private fun ProductListContentPreview() {
MaterialTheme {
ProductListContent(
uiState = ProductUiState.Success,
products = listOf(
Product(1, "产品A", 99.0, ""),
Product(2, "产品B", 199.0, "")
),
onRefresh = {},
onProductClick = {},
onSearchQueryChange = {}
)
}
}
3.4 UDF(Unidirectional Data Flow)架构模式
┌──────────────────────────────────┐
│ ViewModel │
│ ┌──────┐ ┌────────────────┐ │
User Action │ │Action│ → │ Business Logic │ │
──────────────► │ └──────┘ └────────────────┘ │
│ │ │
│ ┌────────▼───────────┐ │
│ │ UiState │ │
│ └────────────────────┘ │
└──────────────────┬───────────────┘
│ StateFlow
▼
┌──────────────────────────────────┐
│ UI Layer │
│ collectAsStateWithLifecycle() │
└──────────────────────────────────┘
kotlin
// 完整 UDF 示例:购物车
data class CartUiState(
val items: List<CartItem> = emptyList(),
val totalPrice: Double = 0.0,
val isLoading: Boolean = false,
val errorMessage: String? = null,
val isCheckoutEnabled: Boolean = false
) {
val isEmpty: Boolean get() = items.isEmpty()
val itemCount: Int get() = items.sumOf { it.quantity }
}
sealed class CartAction {
data class AddItem(val product: Product) : CartAction()
data class RemoveItem(val productId: Int) : CartAction()
data class UpdateQuantity(val productId: Int, val quantity: Int) : CartAction()
object ClearCart : CartAction()
object Checkout : CartAction()
}
sealed class CartEvent {
object NavigateToCheckout : CartEvent()
data class ShowError(val message: String) : CartEvent()
data class ShowItemAddedSnackbar(val productName: String) : CartEvent()
}
@HiltViewModel
class CartViewModel @Inject constructor(
private val cartRepository: CartRepository
) : ViewModel() {
private val _uiState = MutableStateFlow(CartUiState())
val uiState: StateFlow<CartUiState> = _uiState.asStateFlow()
private val _events = MutableSharedFlow<CartEvent>()
val events: SharedFlow<CartEvent> = _events.asSharedFlow()
init {
observeCart()
}
private fun observeCart() {
viewModelScope.launch {
cartRepository.observeCart().collect { items ->
_uiState.update { state ->
state.copy(
items = items,
totalPrice = items.sumOf { it.product.price * it.quantity },
isCheckoutEnabled = items.isNotEmpty()
)
}
}
}
}
// 单入口 action 处理(类 MVI 风格)
fun dispatch(action: CartAction) {
viewModelScope.launch {
when (action) {
is CartAction.AddItem -> addItem(action.product)
is CartAction.RemoveItem -> removeItem(action.productId)
is CartAction.UpdateQuantity -> updateQuantity(action.productId, action.quantity)
CartAction.ClearCart -> clearCart()
CartAction.Checkout -> checkout()
}
}
}
private suspend fun addItem(product: Product) {
cartRepository.addItem(product)
_events.emit(CartEvent.ShowItemAddedSnackbar(product.name))
}
private suspend fun removeItem(productId: Int) {
cartRepository.removeItem(productId)
}
private suspend fun updateQuantity(productId: Int, quantity: Int) {
if (quantity <= 0) {
removeItem(productId)
} else {
cartRepository.updateQuantity(productId, quantity)
}
}
private suspend fun clearCart() {
cartRepository.clearCart()
}
private suspend fun checkout() {
_uiState.update { it.copy(isLoading = true) }
try {
cartRepository.checkout()
_events.emit(CartEvent.NavigateToCheckout)
} catch (e: Exception) {
_events.emit(CartEvent.ShowError(e.message ?: "结算失败"))
} finally {
_uiState.update { it.copy(isLoading = false) }
}
}
}
// UI 层
@Composable
fun CartScreen(
viewModel: CartViewModel = hiltViewModel(),
onNavigateToCheckout: () -> Unit
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val snackbarHostState = remember { SnackbarHostState() }
// 处理一次性事件
LaunchedEffect(Unit) {
viewModel.events.collect { event ->
when (event) {
CartEvent.NavigateToCheckout -> onNavigateToCheckout()
is CartEvent.ShowError -> snackbarHostState.showSnackbar(event.message)
is CartEvent.ShowItemAddedSnackbar ->
snackbarHostState.showSnackbar("${event.productName} 已加入购物车")
}
}
}
Scaffold(snackbarHost = { SnackbarHost(snackbarHostState) }) { padding ->
CartContent(
uiState = uiState,
onAction = viewModel::dispatch,
modifier = Modifier.padding(padding)
)
}
}
3.5 复杂 UI 状态建模
Sealed Class 状态建模
kotlin
// 推荐模式:单一 UiState 类(包含所有状态字段)
@Immutable
data class HomeUiState(
val isLoading: Boolean = false,
val banners: List<Banner> = emptyList(),
val products: List<Product> = emptyList(),
val recommendedUsers: List<User> = emptyList(),
val errorMessage: String? = null,
val isRefreshing: Boolean = false
) {
val hasError: Boolean get() = errorMessage != null
val isEmpty: Boolean get() = products.isEmpty() && !isLoading
// 计算属性:不存储,每次根据状态计算
val sectionedContent: List<HomeSection> get() = buildList {
if (banners.isNotEmpty()) add(HomeSection.BannerSection(banners))
if (products.isNotEmpty()) add(HomeSection.ProductSection(products))
if (recommendedUsers.isNotEmpty()) add(HomeSection.UserSection(recommendedUsers))
}
}
sealed class HomeSection {
data class BannerSection(val banners: List<Banner>) : HomeSection()
data class ProductSection(val products: List<Product>) : HomeSection()
data class UserSection(val users: List<User>) : HomeSection()
}
// Result 封装(通用请求结果)
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val exception: Throwable, val message: String? = null) : Result<Nothing>()
object Loading : Result<Nothing>()
val isSuccess: Boolean get() = this is Success
val isError: Boolean get() = this is Error
val isLoading: Boolean get() = this is Loading
fun getOrNull(): T? = (this as? Success)?.data
inline fun onSuccess(action: (T) -> Unit): Result<T> {
if (this is Success) action(data)
return this
}
inline fun onError(action: (Throwable) -> Unit): Result<T> {
if (this is Error) action(exception)
return this
}
}
// 在 Repository 中使用
class ProductRepository @Inject constructor(
private val api: ProductApi,
private val db: ProductDao
) {
suspend fun getProducts(): Result<List<Product>> = runCatching {
val products = api.fetchProducts()
db.insertAll(products)
products.map { it.toDomain() }
}.fold(
onSuccess = { Result.Success(it) },
onFailure = { Result.Error(it, it.message) }
)
}
分页状态
kotlin
@HiltViewModel
class ProductListViewModel @Inject constructor(
private val repository: ProductRepository
) : ViewModel() {
// Paging 3 与 ViewModel 集成
val pagingData: Flow<PagingData<Product>> = repository
.getProductsPaged()
.cachedIn(viewModelScope) // 缓存在 ViewModel 作用域内
// 组合 Paging 与其他状态
private val _filters = MutableStateFlow(ProductFilter())
val filters: StateFlow<ProductFilter> = _filters.asStateFlow()
val filteredPagingData: Flow<PagingData<Product>> = _filters
.flatMapLatest { filter ->
repository.getProductsPaged(filter).cachedIn(viewModelScope)
}
fun applyFilter(filter: ProductFilter) {
_filters.value = filter
}
}
data class ProductFilter(
val category: String? = null,
val priceRange: ClosedRange<Double>? = null,
val sortBy: SortOption = SortOption.DEFAULT
)
enum class SortOption { DEFAULT, PRICE_ASC, PRICE_DESC, NEWEST }
3.6 SavedStateHandle 与进程恢复
kotlin
@HiltViewModel
class SearchViewModel @Inject constructor(
private val searchRepository: SearchRepository,
savedStateHandle: SavedStateHandle
) : ViewModel() {
// ✅ 从 SavedStateHandle 获取导航参数(Navigation 自动填充)
private val initialQuery: String = savedStateHandle["search_query"] ?: ""
// ✅ 使用 SavedStateHandle 持久化 UI 状态(进程重建后恢复)
// 注意:只能存储可序列化的数据
var searchQuery by savedStateHandle.saveable { mutableStateOf(initialQuery) }
private set
// 或者使用 Flow 方式
private val _query = savedStateHandle.getStateFlow("query", "")
val query: StateFlow<String> = _query
val searchResults: StateFlow<List<SearchResult>> = _query
.debounce(300L)
.distinctUntilChanged()
.flatMapLatest { query ->
if (query.length < 2) flowOf(emptyList())
else searchRepository.search(query)
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
fun onQueryChange(newQuery: String) {
savedStateHandle["query"] = newQuery // 自动持久化
}
}
// 获取 Navigation 传递的参数(推荐方式)
@HiltViewModel
class ProductDetailViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val productRepository: ProductRepository
) : ViewModel() {
// 从路由参数获取 productId
// 配合 Navigation Compose: NavBackStackEntry.arguments
private val productId: Int = checkNotNull(savedStateHandle["productId"])
private val _uiState = MutableStateFlow<ProductDetailUiState>(ProductDetailUiState.Loading)
val uiState: StateFlow<ProductDetailUiState> = _uiState.asStateFlow()
init { loadProduct() }
private fun loadProduct() {
viewModelScope.launch {
_uiState.value = try {
val product = productRepository.getProduct(productId)
ProductDetailUiState.Success(product)
} catch (e: ProductNotFoundException) {
ProductDetailUiState.NotFound
} catch (e: Exception) {
ProductDetailUiState.Error(e.message ?: "加载失败")
}
}
}
}
sealed class ProductDetailUiState {
object Loading : ProductDetailUiState()
data class Success(val product: Product) : ProductDetailUiState()
data class Error(val message: String) : ProductDetailUiState()
object NotFound : ProductDetailUiState()
}
Demo 代码:chapter03
kotlin
// chapter03/StateManagementDemo.kt
package com.example.androiddemos.chapter03
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
// 简化的 ViewModel Demo(不依赖 Hilt)
class Chapter03ViewModel : ViewModel() {
private val _uiState = MutableStateFlow(TodoUiState())
val uiState: StateFlow<TodoUiState> = _uiState.asStateFlow()
private val _events = MutableSharedFlow<String>()
val events: SharedFlow<String> = _events.asSharedFlow()
fun addTodo(text: String) {
if (text.isBlank()) {
viewModelScope.launch { _events.emit("任务不能为空") }
return
}
_uiState.update { state ->
val newItem = TodoItem(
id = System.currentTimeMillis().toInt(),
text = text,
isDone = false
)
state.copy(items = state.items + newItem)
}
}
fun toggleTodo(id: Int) {
_uiState.update { state ->
state.copy(
items = state.items.map { item ->
if (item.id == id) item.copy(isDone = !item.isDone) else item
}
)
}
}
fun removeTodo(id: Int) {
_uiState.update { state ->
state.copy(items = state.items.filter { it.id != id })
}
viewModelScope.launch { _events.emit("任务已删除") }
}
fun loadDemoData() {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true) }
delay(1000L) // 模拟网络请求
_uiState.update { state ->
state.copy(
isLoading = false,
items = listOf(
TodoItem(1, "学习 Kotlin 协程", false),
TodoItem(2, "深入 Jetpack Compose", false),
TodoItem(3, "掌握 Clean Architecture", true)
)
)
}
}
}
}
data class TodoUiState(
val items: List<TodoItem> = emptyList(),
val isLoading: Boolean = false
) {
val completedCount: Int get() = items.count { it.isDone }
val totalCount: Int get() = items.size
}
data class TodoItem(val id: Int, val text: String, val isDone: Boolean)
@Composable
fun Chapter03DemoScreen() {
val viewModel = remember { Chapter03ViewModel() }
val uiState by viewModel.uiState.collectAsState()
val snackbarHostState = remember { SnackbarHostState() }
var inputText by remember { mutableStateOf("") }
LaunchedEffect(Unit) {
viewModel.events.collect { message ->
snackbarHostState.showSnackbar(message)
}
}
Scaffold(snackbarHost = { SnackbarHost(snackbarHostState) }) { padding ->
Column(
modifier = Modifier
.padding(padding)
.padding(16.dp)
.fillMaxSize()
) {
Text("状态管理 Demo", style = MaterialTheme.typography.headlineMedium)
Spacer(Modifier.height(8.dp))
// 进度显示
Text("完成: ${uiState.completedCount} / ${uiState.totalCount}")
Spacer(Modifier.height(16.dp))
// 输入区域
Row(verticalAlignment = Alignment.CenterVertically) {
OutlinedTextField(
value = inputText,
onValueChange = { inputText = it },
placeholder = { Text("新任务...") },
modifier = Modifier.weight(1f)
)
Spacer(Modifier.width(8.dp))
Button(onClick = {
viewModel.addTodo(inputText)
inputText = ""
}) { Text("添加") }
}
Spacer(Modifier.height(8.dp))
Button(
onClick = viewModel::loadDemoData,
modifier = Modifier.fillMaxWidth()
) { Text("加载演示数据") }
Spacer(Modifier.height(16.dp))
if (uiState.isLoading) {
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
} else {
LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
items(uiState.items, key = { it.id }) { item ->
TodoItemCard(
item = item,
onToggle = { viewModel.toggleTodo(item.id) },
onDelete = { viewModel.removeTodo(item.id) }
)
}
}
}
}
}
}
@Composable
private fun TodoItemCard(
item: TodoItem,
onToggle: () -> Unit,
onDelete: () -> Unit
) {
Card(modifier = Modifier.fillMaxWidth()) {
Row(
modifier = Modifier.padding(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(checked = item.isDone, onCheckedChange = { onToggle() })
Text(
text = item.text,
modifier = Modifier.weight(1f).padding(horizontal = 8.dp),
style = if (item.isDone) {
MaterialTheme.typography.bodyMedium.copy(
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f)
)
} else MaterialTheme.typography.bodyMedium
)
IconButton(onClick = onDelete) {
Icon(
imageVector = androidx.compose.material.icons.Icons.Default.Delete,
contentDescription = "删除",
tint = MaterialTheme.colorScheme.error
)
}
}
}
}
章节总结
| 知识点 | 必掌握程度 | 面试频率 |
|---|---|---|
| ViewModel 生命周期原理 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| LiveData vs StateFlow | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| SharedFlow 一次性事件 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| Flow 操作符(debounce / flatMapLatest / combine) | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| stateIn / SharingStarted | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| UDF 单向数据流 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| UiState 建模(sealed class) | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| SavedStateHandle 进程恢复 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| collectAsStateWithLifecycle | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| repeatOnLifecycle | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
👉 下一章:第四章------导航与路由