Kotlin Flow流
一 Kotlin Flow 中的 stateIn 和 shareIn
一、简单比喻理解
想象一个水龙头(数据源)和几个水杯(数据接收者):
- 普通 Flow(冷流):每个水杯来接水时,都要重新打开水龙头从头放水
- stateIn/shareIn(热流):水龙头一直开着,水存在一个水池里,任何水杯随时来接都能拿到水
二、stateIn 是什么?
就像手机的状态栏
- 总是显示最新的一条信息(有
当前值
) - 新用户打开手机时,立刻能看到最后一条消息
- 适合用来表示"当前状态",比如:
- 用户登录状态(已登录/未登录)
- 页面加载状态(加载中/成功/失败)
- 实时更新的数据(如股票价格)
代码示例:
// 创建一个永远知道当前温度的温度计
val currentTemperature = sensorFlow.stateIn(scope = viewModelScope, // 在ViewModel生命周期内有效started = SharingStarted.WhileSubscribed(5000), // 5秒无订阅就暂停initialValue = 0 // 初始温度0度)// 在Activity中读取(总是能拿到当前温度)
textView.text = "${currentTemperature.value}°C"
三、shareIn 是什么?
就像广播电台
- 不保存"当前值"(没有
.value
属性) - 新听众打开收音机时,可以选择:
- 从最新的一条新闻开始听(replay=1)
- 只听新新闻(replay=0)
- 适合用来处理"事件",比如:
- 显示Toast提示
- 页面跳转指令
- 一次性通知
代码示例:
// 创建一个消息广播站
val messages = notificationFlow.shareIn(scope = viewModelScope,started = SharingStarted.Lazily, // 有人收听时才启动replay = 1 // 新听众能听到最后1条消息)// 在Activity中收听广播
lifecycleScope.launch {messages.collect { msg ->Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()}
}
四、主要区别对比
特性 | stateIn (状态栏) | shareIn (广播电台) |
---|---|---|
有无当前值 | 有(.value 直接访问) | 无(必须通过collect接收) |
新订阅者 | 立即获得最新值 | 可配置获得最近N条(replay) |
典型用途 | 持续更新的状态(如用户积分) | 一次性事件(如"购买成功"提示) |
内存占用 | 始终保存最新值 | 按需缓存(可配置) |
是否热流 | 是 | 是 |
五、为什么要用它们?
-
节省资源:避免重复计算(多个界面可以共享同一个数据源)
- ❌ 不用时:每个界面都单独请求一次网络数据
- ✅ 使用后:所有界面共享同一份网络数据
-
保持一致性:所有订阅者看到的数据完全相同
- 比如用户头像更新后,所有界面立即同步
-
自动管理生命周期:
- 当Activity销毁时自动停止收集
- 当配置变更(如屏幕旋转)时保持数据不丢失
六、生活场景类比
场景1:微信群(stateIn)
- 群里最后一条消息就是当前状态(.value)
- 新成员进群立刻能看到最后一条消息
- 适合:工作群的状态同步
场景2:电台广播(shareIn)
- 主播不断发送新消息
- 听众打开收音机时:
- 可以设置是否听之前的回放(replay)
- 但无法直接问"刚才最后一首歌是什么"(无.value)
- 适合:交通路况实时播报
七、什么时候用哪个?
用 stateIn 当:
- 需要随时知道"当前值"
- 数据会持续变化且需要被多个地方使用
- 例如:
- 用户登录状态
- 购物车商品数量
- 实时位置更新
用 shareIn 当:
- 只关心新事件,不关心历史值
- 事件可能被多个接收者处理
- 例如:
- "订单支付成功"通知
- 错误提示消息
- 页面跳转指令
八、超简单选择流程图
要管理持续变化的状态吗?是 → 需要直接访问当前值吗?是 → 用 stateIn否 → 用 shareIn(replay=1)否 → 这是一次性事件吗?是 → 用 shareIn(replay=0)
记住这个简单的口诀:
“状态用state,事件用share,想要回放加replay”
二 Kotlin Flow 的 shareIn
和 stateIn
操作符完全指南
在 Kotlin Flow 的使用中,shareIn
和 stateIn
是两个关键的操作符,用于优化流的共享和状态管理。本教程将深入解析这两个操作符的使用场景、区别和最佳实践。
一、核心概念解析
1. 冷流 vs 热流
- 冷流 (Cold Flow):每个收集者都会触发独立的执行(如普通的
flow{}
构建器) - 热流 (Hot Flow):数据发射独立于收集者存在(如
StateFlow
、SharedFlow
)
2. 为什么需要 shareIn
/stateIn
?
- 避免对上游冷流进行重复计算
- 多个收集者共享同一个数据源
- 将冷流转换为热流以提高效率
二、stateIn
操作符详解
基本用法
val sharedFlow: StateFlow<Int> = flow {// 模拟耗时操作emit(repository.fetchData())
}.stateIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(),initialValue = 0
)
参数说明:
- scope:共享流的协程作用域(通常用
viewModelScope
) - started:共享启动策略(后文详细讲解)
- initialValue:必须提供的初始值
特点:
- 总是有当前值(通过
value
属性访问) - 新收集者立即获得最新值
- 适合表示 UI 状态
使用场景示例:
用户个人信息状态管理
class UserViewModel : ViewModel() {private val _userState = repository.userUpdates() // Flow<User>.map { it.toUiState() }.stateIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(5000),initialValue = UserState.Loading)val userState: StateFlow<UserState> = _userState
}
三、shareIn
操作符详解
基本用法
val sharedFlow: SharedFlow<Int> = flow {emit(repository.fetchData())
}.shareIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(),replay = 1
)
参数说明:
- replay:新收集者接收的旧值数量
- extraBufferCapacity:超出 replay 的缓冲大小
- onBufferOverflow:缓冲策略(
SUSPEND
,DROP_OLDEST
,DROP_LATEST
)
特点:
- 可以有多个订阅者
- 没有
value
属性,必须通过收集获取数据 - 适合事件处理(如 Toast、导航事件)
使用场景示例:
全局事件通知
class EventBus {private val _events = MutableSharedFlow<Event>()val events = _events.asSharedFlow()suspend fun postEvent(event: Event) {_events.emit(event)}// 使用 shareIn 转换外部流val externalEvents = someExternalFlow.shareIn(scope = CoroutineScope(Dispatchers.IO),started = SharingStarted.Eagerly,replay = 0)
}
四、started
参数深度解析
1. SharingStarted.Eagerly
- 行为:立即启动,无视是否有收集者
- 用例:需要预先缓存的数据
- 风险:可能造成资源浪费
started = SharingStarted.Eagerly
2. SharingStarted.Lazily
- 行为:在第一个收集者出现时启动,保持活跃直到 scope 结束
- 用例:长期存在的共享数据
- 注意:可能延迟首次数据获取
started = SharingStarted.Lazily
3. SharingStarted.WhileSubscribed()
- 行为:
- 有收集者时活跃
- 最后一个收集者消失后保持一段时间(默认 0ms)
- 可配置
stopTimeoutMillis
和replayExpirationMillis
- 用例:大多数 UI 相关状态
// 保留5秒供可能的重新订阅
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000)
五、关键区别对比
特性 | stateIn | shareIn |
---|---|---|
返回类型 | StateFlow | SharedFlow |
初始值 | 必须提供 | 无要求 |
新收集者获取 | 立即获得最新 value | 获取 replay 数量的旧值 |
值访问 | 通过 .value 直接访问 | 必须通过收集获取 |
典型用途 | UI 状态管理 | 事件通知/数据广播 |
背压处理 | 总是缓存最新值 | 可配置缓冲策略 |
六、最佳实践指南
1. ViewModel 中的标准模式
class MyViewModel : ViewModel() {// 状态管理用 stateInval uiState = repository.data.stateIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(5000),initialValue = null)// 事件处理用 shareInval events = repository.events.shareIn(scope = viewModelScope,started = SharingStarted.Lazily,replay = 1)
}
2. 合理选择 started
策略
- UI 状态:
WhileSubscribed(stopTimeoutMillis = 5000)
- 配置变更需保留:
Lazily
- 全局常驻数据:
Eagerly
3. 避免常见错误
错误1:在每次调用时创建新流
// 错误!每次调用都创建新流
fun getUser() = repository.getUserFlow().stateIn(viewModelScope, SharingStarted.Eagerly, null)// 正确:共享同一个流
private val _user = repository.getUserFlow().stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val user: StateFlow<User?> = _user
错误2:忽略 replay 配置
// 可能丢失事件
.shareIn(scope, SharingStarted.Lazily, replay = 0)// 更安全的配置
.shareIn(scope, SharingStarted.Lazily, replay = 1)
七、高级应用场景
1. 结合 Room 数据库
@Dao
interface UserDao {@Query("SELECT * FROM user")fun observeUsers(): Flow<List<User>>
}// ViewModel 中
val users = userDao.observeUsers().stateIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(),initialValue = emptyList())
2. 实现自动刷新功能
val autoRefreshData = flow {while(true) {emit(repository.fetchLatest())delay(30_000) // 每30秒刷新}
}.shareIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(),replay = 1
)
3. 多源数据合并
val combinedData = combine(repo1.data.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 1),repo2.data.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 1)
) { data1, data2 ->data1 + data2
}.stateIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(),initialValue = emptyList()
)
八、性能优化技巧
-
合理设置 replay:
- UI 状态:
replay = 1
(确保新订阅者立即获得状态) - 事件通知:
replay = 0
(避免重复处理旧事件)
- UI 状态:
-
使用 WhileSubscribed 的过期策略:
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000,replayExpirationMillis = 60_000 // 1分钟后丢弃缓存 )
-
避免过度缓冲:
.shareIn(scope = ...,replay = 1,extraBufferCapacity = 1, // 总共缓冲2个值onBufferOverflow = BufferOverflow.DROP_OLDEST )
九、测试策略
1. 测试 StateFlow
@Test
fun testStateFlow() = runTest {val testScope = TestScope()val flow = flowOf(1, 2, 3)val stateFlow = flow.stateIn(scope = testScope,started = SharingStarted.Eagerly,initialValue = 0)assertEquals(0, stateFlow.value) // 初始值testScope.advanceUntilIdle()assertEquals(3, stateFlow.value) // 最后发射的值
}
2. 测试 SharedFlow
@Test
fun testSharedFlow() = runTest {val testScope = TestScope()val flow = flowOf("A", "B", "C")val sharedFlow = flow.shareIn(scope = testScope,started = SharingStarted.Eagerly,replay = 1)val results = mutableListOf<String>()val job = launch {sharedFlow.collect { results.add(it) }}testScope.advanceUntilIdle()assertEquals(listOf("A", "B", "C"), results)job.cancel()
}
十、总结决策树
何时使用 stateIn
?
- 需要表示当前状态(有
.value
属性) - UI 需要立即访问最新值
- 适合:页面状态、表单数据、加载状态
何时使用 shareIn
?
- 处理一次性事件
- 需要自定义缓冲策略
- 适合:Toast 消息、导航事件、广播通知
选择哪种 started
策略?
WhileSubscribed()
:大多数 UI 场景Lazily
:配置变更需保留数据Eagerly
:需要预加载的全局数据
通过本教程,应该已经掌握了 shareIn
和 stateIn
的核心用法和高级技巧。正确使用这两个操作符可以显著提升应用的性能和资源利用率。
三 从 LiveData 迁移到 Kotlin Flow 完整教程
LiveData 长期以来是 Android 架构组件中状态管理的核心,但随着 Kotlin Flow 的成熟,Google 官方推荐将现有 LiveData 迁移到 Flow。本教程基于官方文章并扩展实践细节,完成平滑迁移。
一、为什么要从 LiveData 迁移到 Flow?
LiveData 的局限性
- 有限的运算符:只有简单的 map/switchMap 转换
- 线程限制:只能在主线程观察
- 一次性操作:不适合处理事件流
- 生命周期耦合:虽然方便但也限制了灵活性
Flow 的优势
- 丰富的操作符:filter, transform, combine 等 100+ 操作符
- 灵活的线程控制:通过 Dispatchers 指定执行线程
- 响应式编程:完整的事件流处理能力
- 协程集成:与 Kotlin 协程完美配合
二、基础迁移方案
1. 简单替换:LiveData → StateFlow
原 LiveData 代码:
class MyViewModel : ViewModel() {private val _userName = MutableLiveData("")val userName: LiveData<String> = _userNamefun updateName(name: String) {_userName.value = name}
}
迁移为 StateFlow:
class MyViewModel : ViewModel() {private val _userName = MutableStateFlow("")val userName: StateFlow<String> = _userName.asStateFlow()fun updateName(name: String) {_userName.value = name}
}
2. 在 UI 层收集 Flow
Activity/Fragment 中收集:
lifecycleScope.launch {repeatOnLifecycle(Lifecycle.State.STARTED) {viewModel.userName.collect { name ->binding.textView.text = name}}
}
关键点:使用
repeatOnLifecycle
确保只在 UI 可见时收集,避免资源浪费
三、高级迁移模式
1. 数据流转换(替代 Transformations)
LiveData 方式:
val userName: LiveData<String> = Transformations.map(_userName) { "Hello, $it!"
}
Flow 方式:
val userName: Flow<String> = _userName.map { "Hello, $it!"
}
2. 多数据源合并(替代 MediatorLiveData)
LiveData 方式:
val result = MediatorLiveData<String>().apply {addSource(liveData1) { value = "$it + ${liveData2.value}" }addSource(liveData2) { value = "${liveData1.value} + $it" }
}
Flow 方式:
val result = combine(flow1, flow2) { data1, data2 ->"$data1 + $data2"
}
四、处理一次性事件(替代 SingleLiveEvent)
LiveData 常被滥用处理一次性事件(如 Toast、导航),Flow 提供了更专业的解决方案:
使用 SharedFlow
class EventViewModel : ViewModel() {private val _events = MutableSharedFlow<Event>()val events = _events.asSharedFlow()sealed class Event {data class ShowToast(val message: String) : Event()object NavigateToNext : Event()}fun triggerToast() {viewModelScope.launch {_events.emit(Event.ShowToast("Hello Flow!"))}}
}
UI 层收集:
lifecycleScope.launch {repeatOnLifecycle(Lifecycle.State.STARTED) {viewModel.events.collect { event ->when (event) {is EventViewModel.Event.ShowToast -> Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show()EventViewModel.Event.NavigateToNext -> startActivity(Intent(this, NextActivity::class.java))}}}
}
五、Room 数据库迁移
LiveData 查询:
@Dao
interface UserDao {@Query("SELECT * FROM user")fun getUsers(): LiveData<List<User>>
}
迁移到 Flow:
@Dao
interface UserDao {@Query("SELECT * FROM user")fun getUsers(): Flow<List<User>>
}
ViewModel 中使用:
val users: StateFlow<List<User>> = userDao.getUsers().stateIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(5000),initialValue = emptyList())
六、兼容现有代码的渐进式迁移
1. 使用 asLiveData()
临时兼容
val userFlow: Flow<User> = repository.getUserFlow()// 临时保持 LiveData 接口
val userLiveData: LiveData<User> = userFlow.asLiveData()
2. 混合使用策略
class HybridViewModel : ViewModel() {// 新功能使用 Flowprivate val _newFeatureState = MutableStateFlow("")val newFeatureState: StateFlow<String> = _newFeatureState.asStateFlow()// 旧功能暂保持 LiveDataprivate val _oldFeatureData = MutableLiveData(0)val oldFeatureData: LiveData<Int> = _oldFeatureData
}
七、测试策略调整
LiveData 测试:
@Test
fun testLiveData() {val liveData = MutableLiveData("test")assertEquals("test", liveData.value)
}
Flow 测试:
@Test
fun testFlow() = runTest {val flow = MutableStateFlow("test")val results = mutableListOf<String>()val job = launch {flow.collect { results.add(it) }}flow.value = "new value"assertEquals(listOf("test", "new value"), results)job.cancel()
}
八、性能优化技巧
-
使用
stateIn
共享流:val sharedFlow = someFlow.stateIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(5000),initialValue = null)
-
避免重复创建 Flow:
// 错误方式 - 每次调用都创建新流 fun getUser() = userDao.getUserFlow()// 正确方式 - 共享同一个流 private val _userFlow = userDao.getUserFlow() val userFlow = _userFlow
-
合理选择背压策略:
// 缓冲最新值 val events = MutableSharedFlow<Event>(replay = 0,extraBufferCapacity = 1,onBufferOverflow = BufferOverflow.DROP_OLDEST )
九、常见问题解决方案
Q1: 如何确保 Flow 收集不会泄漏?
lifecycleScope.launch {repeatOnLifecycle(Lifecycle.State.STARTED) {// 只有在此块内会激活收集viewModel.data.collect { ... }}
}
Q2: 为什么我的 Flow 不发射数据?
检查:
- Flow 是否被正确触发(冷流需要收集才会开始)
- 是否在正确的协程作用域内收集
- 是否有异常导致流终止
Q3: 如何处理 Java 代码调用?
// 暴露 LiveData 给 Java 模块
val javaCompatData: LiveData<String> = flow.asLiveData()
十、完整迁移示例
迁移前 ViewModel:
class OldViewModel : ViewModel() {private val _data = MutableLiveData("")val data: LiveData<String> = _dataprivate val _event = SingleLiveEvent<Event>()val event: LiveData<Event> = _eventfun loadData() {viewModelScope.launch {_data.value = repository.fetchData()_event.value = Event.ShowToast("Loaded")}}
}
迁移后 ViewModel:
class NewViewModel : ViewModel() {private val _data = MutableStateFlow("")val data: StateFlow<String> = _data.asStateFlow()private val _event = MutableSharedFlow<Event>()val event: SharedFlow<Event> = _event.asSharedFlow()fun loadData() {viewModelScope.launch {_data.value = repository.fetchData()_event.emit(Event.ShowToast("Loaded"))}}
}
通过本教程,可以系统性地将现有 LiveData 代码迁移到 Kotlin Flow。建议采用渐进式迁移策略,优先在新功能中使用 Flow,逐步改造旧代码。
四 Android StateFlow 完整教程
Android StateFlow 完整教程:从入门到实战
StateFlow 是 Kotlin 协程库中用于状态管理的响应式流,特别适合在 Android 应用开发中管理 UI 状态。本教程将带你全面了解 StateFlow 的使用方法。
1. StateFlow 基础概念
1.1 什么是 StateFlow?
StateFlow 是 Kotlin 协程提供的一种热流(Hot Flow),它具有以下特点:
- 总是有当前值(初始值必须提供)
- 只保留最新值
- 支持多个观察者
- 与 LiveData 类似但基于协程
1.2 StateFlow vs LiveData
特性 | StateFlow | LiveData |
---|---|---|
生命周期感知 | 否(需配合 lifecycleScope) | 是 |
需要初始值 | 是 | 否 |
基于 | 协程 | 观察者模式 |
线程控制 | 通过 Dispatcher | 主线程 |
背压处理 | 自动处理 | 自动处理 |
2. 基本使用
2.1 添加依赖
在 build.gradle 中添加:
dependencies {implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1"
}
2.2 创建 StateFlow
class MyViewModel : ViewModel() {// 私有可变的StateFlowprivate val _uiState = MutableStateFlow<UiState>(UiState.Loading)// 公开不可变的StateFlowval uiState: StateFlow<UiState> = _uiState.asStateFlow()sealed class UiState {object Loading : UiState()data class Success(val data: String) : UiState()data class Error(val message: String) : UiState()}fun loadData() {viewModelScope.launch {_uiState.value = UiState.Loadingtry {val result = repository.fetchData()_uiState.value = UiState.Success(result)} catch (e: Exception) {_uiState.value = UiState.Error(e.message ?: "Unknown error")}}}
}
2.3 在 Activity/Fragment 中收集 StateFlow
class MyActivity : AppCompatActivity() {private val viewModel: MyViewModel by viewModels()override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)lifecycleScope.launch {repeatOnLifecycle(Lifecycle.State.STARTED) {viewModel.uiState.collect { state ->when (state) {is MyViewModel.UiState.Loading -> showLoading()is MyViewModel.UiState.Success -> showData(state.data)is MyViewModel.UiState.Error -> showError(state.message)}}}}}private fun showLoading() { /*...*/ }private fun showData(data: String) { /*...*/ }private fun showError(message: String) { /*...*/ }
}
3. 高级用法
3.1 结合 SharedFlow 处理一次性事件
class EventViewModel : ViewModel() {private val _events = MutableSharedFlow<Event>()val events = _events.asSharedFlow()sealed class Event {data class ShowToast(val message: String) : Event()object NavigateToNextScreen : Event()}fun triggerEvent() {viewModelScope.launch {_events.emit(Event.ShowToast("Hello World!"))}}
}// 在Activity中收集
lifecycleScope.launch {repeatOnLifecycle(Lifecycle.State.STARTED) {viewModel.events.collect { event ->when (event) {is EventViewModel.Event.ShowToast -> showToast(event.message)EventViewModel.Event.NavigateToNextScreen -> navigateToNext()}}}
}
3.2 状态合并 (combine)
val userName = MutableStateFlow("")
val userAge = MutableStateFlow(0)val userInfo = combine(userName, userAge) { name, age ->"Name: $name, Age: $age"
}// 收集合并后的流
userInfo.collect { info ->println(info)
}
3.3 状态转换 (map, filter, etc.)
val numbers = MutableStateFlow(0)val evenNumbers = numbers.filter { it % 2 == 0 }.map { "Even: $it" }evenNumbers.collect { println(it) }
4. 性能优化
4.1 使用 stateIn 缓存 StateFlow
val networkFlow = flow {// 模拟网络请求emit(repository.fetchData())
}val cachedState = networkFlow.stateIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(5000), // 5秒无订阅者停止initialValue = "Loading..."
)
4.2 避免重复收集
// 错误方式 - 每次重组都会创建新的收集器
@Composable
fun MyComposable(viewModel: MyViewModel) {val state by viewModel.state.collectAsState()// ...
}// 正确方式 - 使用 derivedStateOf 或 remember
@Composable
fun MyComposable(viewModel: MyViewModel) {val state by remember { viewModel.state }.collectAsState()// ...
}
5. 测试 StateFlow
5.1 单元测试
@Test
fun `test state flow`() = runTest {val viewModel = MyViewModel()val results = mutableListOf<MyViewModel.UiState>()val job = launch {viewModel.uiState.collect { results.add(it) }}viewModel.loadData()advanceUntilIdle()assertEquals(3, results.size) // Loading, Success/ErrorassertTrue(results[0] is MyViewModel.UiState.Loading)job.cancel()
}
5.2 使用 Turbine 测试库
dependencies {testImplementation "app.cash.turbine:turbine:0.12.1"
}@Test
fun `test with turbine`() = runTest {val viewModel = MyViewModel()viewModel.uiState.test {viewModel.loadData()assertEquals(MyViewModel.UiState.Loading, awaitItem())val success = awaitItem()assertTrue(success is MyViewModel.UiState.Success)cancelAndIgnoreRemainingEvents()}
}
6. 常见问题解答
Q1: StateFlow 和 LiveData 哪个更好?
StateFlow 更适合协程环境,LiveData 更简单但功能较少。新项目推荐 StateFlow。
Q2: 如何处理背压(Backpressure)?
StateFlow 自动处理背压,只保留最新值。
Q3: 为什么我的收集器没有收到更新?
检查:
- 是否在正确的生命周期范围内收集
- Flow 是否有发射新值
- 是否在正确的协程上下文中
Q4: 如何避免内存泄漏?
使用 repeatOnLifecycle
或 flowWithLifecycle
确保只在活跃生命周期收集。
7. 完整示例项目
以下是一个完整的 ViewModel 示例:
class UserViewModel(private val userRepository: UserRepository) : ViewModel() {private val _userState = MutableStateFlow<UserState>(UserState.Loading)val userState: StateFlow<UserState> = _userState.asStateFlow()private val _events = MutableSharedFlow<UserEvent>()val events: SharedFlow<UserEvent> = _events.asSharedFlow()init {loadUser()}fun loadUser() {viewModelScope.launch {_userState.value = UserState.Loadingtry {val user = userRepository.getUser()_userState.value = UserState.Success(user)} catch (e: Exception) {_userState.value = UserState.Error(e.message ?: "Unknown error")_events.emit(UserEvent.ShowErrorToast("Failed to load user"))}}}fun updateUserName(name: String) {viewModelScope.launch {val currentUser = (_userState.value as? UserState.Success)?.user ?: return@launchval updatedUser = currentUser.copy(name = name)_userState.value = UserState.Success(updatedUser)userRepository.updateUser(updatedUser)}}sealed class UserState {object Loading : UserState()data class Success(val user: User) : UserState()data class Error(val message: String) : UserState()}sealed class UserEvent {data class ShowErrorToast(val message: String) : UserEvent()}
}
通过本教程,你应该已经掌握了 StateFlow 的核心用法。StateFlow 是构建响应式 Android 应用的强大工具,结合协程可以提供更简洁、更安全的状态管理方案。