第三章:状态管理与 Jetpack 架构组件

本章深入 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 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐

👉 下一章:第四章------导航与路由

相关推荐
峥嵘life2 小时前
Android + Kiro AI软件开发实战教程
android·后端·学习
IT邦德2 小时前
Update Advisor:Oracle MAA架构下数据库补丁管理
数据库·oracle·架构
流星雨在线2 小时前
安卓路由技术选型调研
android·therouter·arouter
richard_yuu2 小时前
深度解析:多层次与多视图软件架构
架构·个人开发
千里马学框架2 小时前
Ubuntu 24 搭建aosp源码环境详细笔记
android·linux·ubuntu·framework·安卓·aosp·源码环境
空中海3 小时前
安卓 第六章:主题、样式与国际化
android
Ehtan_Zheng3 小时前
7个Kotlin Delegate
android
用户69371750013843 小时前
2026 Android 开发,现在还能入行吗?
android·前端·ai编程
YBZha3 小时前
Android Camera2 + OpenGL 竖屏或横屏预览会有“轻微拉伸”
android