
前言前面课程已经接触过 StateFlow 用于保存页面UI状态。Flow是Kotlin协程的响应式数据流在Android ViewModel里面大量用于异步、网络、数据库的数据响应。本节课重点区分StateFlow 和 SharedFlow并且演示ViewModel当中模拟网络加载数据UI层通过Compose收集数据流渲染页面。前置依赖dependencies { implementation org.jetbrains.kotlinx:kotlinx-coroutines‑android:1.9.0 }知识点清单StateFlow有状态数据流保存最新值新订阅者直接拿到当前最新的值SharedFlow无状态事件数据流用来发送一次性事件Toast、弹窗、导航提示replay参数含义冷流、热流简单概念ViewModel中模拟网络异步加载UI层使用collectAsStateWithLifecycle收集StateFlow使用LaunchedEffect收集SharedFlow一次性事件完整示例源码importandroidx.lifecycle.ViewModelimportandroidx.lifecycle.viewModelScopeimportkotlinx.coroutines.delayimportkotlinx.coroutines.flow.MutableStateFlowimportkotlinx.coroutines.flow.StateFlowimportkotlinx.coroutines.flow.MutableSharedFlowimportkotlinx.coroutines.flow.SharedFlowimportkotlinx.coroutines.launchimportandroidx.compose.runtime.Composableimportandroidx.compose.runtime.collectAsStateWithLifecycleimportandroidx.compose.runtime.getValueimportandroidx.compose.runtime.LaunchedEffectimportandroidx.compose.ui.Modifierimportandroidx.compose.foundation.layout.Arrangementimportandroidx.compose.foundation.layout.Columnimportandroidx.compose.foundation.layout.fillMaxSizeimportandroidx.compose.material3.Buttonimportandroidx.compose.material3.CircularProgressIndicatorimportandroidx.compose.material3.TextclassArticleViewModel:ViewModel(){//StateFlow保存页面UI状态加载中 / 成功文本privateval_contentStateFlowMutableStateFlowString()valcontentStateFlow:StateFlowString_contentStateFlow//SharedFlow一次性事件用来发出toast提示消息privateval_toastEventFlowMutableSharedFlowString()valtoastEventFlow:SharedFlowString_toastEventFlowfunloadArticle(){viewModelScope.launch{_contentStateFlow.value正在加载……delay(2000)//模拟网络请求耗时valresult文章正文Kotlin‑Flow学习演示数据_contentStateFlow.valueresult//发送一次性toast事件_toastEventFlow.emit(网络数据加载完成)}}}ComposablefunArticleLoadPage(){valvm:ArticleViewModelandroidx.lifecycle.viewmodel.compose.viewModel()valpageContentbyvm.contentStateFlow.collectAsStateWithLifecycle()//收集一次性SharedFlow事件必须使用LaunchedEffectLaunchedEffect(Unit){vm.toastEventFlow.collect{msg-// 这里接收到一次性事件实际项目调用Toast弹出提示println([模拟Toast]$msg)}}Column(modifierModifier.fillMaxSize(),verticalArrangementArrangement.Center,horizontalAlignmentandroidx.compose.ui.Alignment.CenterHorizontally){if(pageContent正在加载……){CircularProgressIndicator()}Text(textpageContent)Button(onClick{vm.loadArticle()}){Text(点击加载网络文章)}}}逐行解读StateFlow保存UI状态页面显示的数据MutableStateFlow 内部始终持有一份当前值当新的订阅者开始收集的时候可以立刻拿到最近的一个值适合页面UI状态列表数据、文本、加载状态。UI层 collectAsStateWithLifecycle() 把Flow转为Compose可观察状态驱动界面重组。SharedFlow一次性事件弹窗、Toast、导航SharedFlow不保存状态事件发出之后如果此时没有订阅者接收这条事件直接就丢失。适合一次性的消息提示不适合存放页面显示数据。❗SharedFlow不能使用collectAsStateWithLifecycle必须放在 LaunchedEffect 协程里面手动collect收集。replay 参数简要说明MutableSharedFlow(replay 1) replay1代表缓存最近1条事件新订阅上来之后可以重放这条消息开发中要注意replay过大容易造成旧事件重复消费弹窗重复弹出bug。viewModelScope执行模拟网络请求viewModelScope.launch{ } 发起异步内部delay模拟网络网络返回之后修改StateFlow的value更新UI状态同时emit发送SharedFlow一次性提示事件。ViewModel销毁的时候viewModelScope自动取消网络请求自动终止防止内存泄漏。重要开发原则页面上渲染的数据优先存放到StateFlow短暂一次性的事件toast、弹窗、跳转指令交给SharedFlow禁止拿StateFlow去做一次性事件页面重组之后会重复触发弹窗。容易踩坑❌ 使用StateFlow来发送Toast弹窗事件屏幕重组之后旧状态还存在弹窗会反复弹出。❌ SharedFlow尝试调用collectAsStateWithLifecycle编译报错SharedFlow不是StateFlow不能转为界面状态。❌ SharedFlow不设置合适replay页面重建之后重复消费旧事件多次弹窗。✅ StateFlow保存UI状态 → collectAsStateWithLifecycle✅ SharedFlow一次性事件 → LaunchedEffect collect。多元化习题练习习题1【基础‑改错题】题目业务希望发出一次性弹窗提示开发者错误选用MutableStateFlow存放弹窗消息。分析会带来什么bug答案StateFlow会保存这条提示字符串页面发生重组之后UI读取该状态弹窗会反复弹出弹窗属于一次性事件应当改用MutableSharedFlow发送事件。解读区分状态与事件是Flow开发非常关键的思维状态是页面上持续展示的数据事件是瞬时发生一次的通知。习题2【基础‑代码修改题】ViewModel里面增加一个刷新方法refreshData延时1500ms之后修改contentStateFlow的值为“文章已经刷新完成”同时通过toastEventFlow发出事件“刷新完毕”写出该refreshData函数。答案funrefreshData(){viewModelScope.launch{delay(1500)_contentStateFlow.value文章已经刷新完成_toastEventFlow.emit(刷新完毕)}}解读viewModelScope.launch开启协程delay模拟网络修改状态使用value发送瞬时事件使用emit。习题3【进阶‑补充代码题】请写出UI层收集SharedFlow事件的LaunchedEffect代码片段只写收集部分答案LaunchedEffect(Unit){vm.toastEventFlow.collect{message-println(收到事件$message)}}解读LaunchedEffect(Unit)页面首次组合的时候启动协程持续订阅SharedFlow数据流页面离开之后Effect自动取消订阅。习题4【综合‑逻辑应用题】网络请求的时候出现异常IO失败网络异常应该作为UI状态还是一次性事件答案两种场景① 如果是短暂的错误提示弹窗只弹一次作为SharedFlow一次性事件② 如果页面上要长期展示“加载失败请重试”文字按钮这个失败信息属于页面UI状态放到StateFlow保存。解读同一个错误看UI表现是页面常驻展示还是瞬时弹窗决定使用StateFlow还是SharedFlow。习题5【概念简答题】请简述StateFlow与SharedFlow核心区别。答案StateFlow内部保存最新状态值新订阅者可以立刻拿到当前的值适合保存页面UI状态UI层可使用collectAsStateWithLifecycle。SharedFlow默认不保存状态事件发出若无订阅者则直接丢失用于一次性瞬时事件如Toast、弹窗、导航只能在LaunchedEffect协程中collect收集。可以设置replay缓存重放若干条消息。解读分清状态与事件是ViewModelFlow开发的基石很多奇怪bug都是混淆二者产生。本节课知识点总结StateFlow持有最新状态适合页面常驻UI数据UI层collectAsStateWithLifecycle收集驱动重组。SharedFlow瞬时事件流用于toast、弹窗、导航使用LaunchedEffect协程手动collectreplay控制重放缓存。viewModelScope.launch执行异步网络ViewModel销毁协程自动取消避免内存泄漏。开发原则常驻UI数据放StateFlow瞬时一次性事件使用SharedFlow不要混用。replay参数控制SharedFlow消息重放过大容易造成重复弹窗bug。下一课预告进阶第6课处理网络异常、加载/错误/空页面UI状态封装密封类管理页面状态