一 Kotlin Flow 中的 stateIn 和 shareIn
一、简单比喻理解
想象一个水龙头(数据源)和几个水杯(数据接收者):
- 普通 Flow(冷流):每个水杯来接水时,都要重新打开水龙头从头放水
- stateIn/shareIn(热流):水龙头一直开着,水存在一个水池里,任何水杯随时来接都能拿到水
二、stateIn 是什么?
就像手机的状态栏
- 总是显示最新的一条信息(有
当前值
) - 新用户打开手机时,立刻能看到最后一条消息
- 适合用来表示"当前状态",比如:
- 用户登录状态(已登录/未登录)
- 页面加载状态(加载中/成功/失败)
- 实时更新的数据(如股票价格)
代码示例:
kotlin
// 创建一个永远知道当前温度的温度计
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提示
- 页面跳转指令
- 一次性通知
代码示例:
kotlin
// 创建一个消息广播站
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
操作符详解
基本用法
kotlin
val sharedFlow: StateFlow<Int> = flow {
// 模拟耗时操作
emit(repository.fetchData())
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(),
initialValue = 0
)
参数说明:
- scope :共享流的协程作用域(通常用
viewModelScope
) - started:共享启动策略(后文详细讲解)
- initialValue:必须提供的初始值
特点:
- 总是有当前值(通过
value
属性访问) - 新收集者立即获得最新值
- 适合表示 UI 状态
使用场景示例:
用户个人信息状态管理
kotlin
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
操作符详解
基本用法
kotlin
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、导航事件)
使用场景示例:
全局事件通知
kotlin
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
- 行为:立即启动,无视是否有收集者
- 用例:需要预先缓存的数据
- 风险:可能造成资源浪费
kotlin
started = SharingStarted.Eagerly
2. SharingStarted.Lazily
- 行为:在第一个收集者出现时启动,保持活跃直到 scope 结束
- 用例:长期存在的共享数据
- 注意:可能延迟首次数据获取
kotlin
started = SharingStarted.Lazily
3. SharingStarted.WhileSubscribed()
- 行为 :
- 有收集者时活跃
- 最后一个收集者消失后保持一段时间(默认 0ms)
- 可配置
stopTimeoutMillis
和replayExpirationMillis
- 用例:大多数 UI 相关状态
kotlin
// 保留5秒供可能的重新订阅
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000)
五、关键区别对比
特性 | stateIn |
shareIn |
---|---|---|
返回类型 | StateFlow |
SharedFlow |
初始值 | 必须提供 | 无要求 |
新收集者获取 | 立即获得最新 value |
获取 replay 数量的旧值 |
值访问 | 通过 .value 直接访问 |
必须通过收集获取 |
典型用途 | UI 状态管理 | 事件通知/数据广播 |
背压处理 | 总是缓存最新值 | 可配置缓冲策略 |
六、最佳实践指南
1. ViewModel 中的标准模式
kotlin
class MyViewModel : ViewModel() {
// 状态管理用 stateIn
val uiState = repository.data
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = null
)
// 事件处理用 shareIn
val events = repository.events
.shareIn(
scope = viewModelScope,
started = SharingStarted.Lazily,
replay = 1
)
}
2. 合理选择 started
策略
- UI 状态 :
WhileSubscribed(stopTimeoutMillis = 5000)
- 配置变更需保留 :
Lazily
- 全局常驻数据 :
Eagerly
3. 避免常见错误
错误1:在每次调用时创建新流
kotlin
// 错误!每次调用都创建新流
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 配置
kotlin
// 可能丢失事件
.shareIn(scope, SharingStarted.Lazily, replay = 0)
// 更安全的配置
.shareIn(scope, SharingStarted.Lazily, replay = 1)
七、高级应用场景
1. 结合 Room 数据库
kotlin
@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. 实现自动刷新功能
kotlin
val autoRefreshData = flow {
while(true) {
emit(repository.fetchLatest())
delay(30_000) // 每30秒刷新
}
}.shareIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(),
replay = 1
)
3. 多源数据合并
kotlin
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 的过期策略:
kotlinstarted = SharingStarted.WhileSubscribed( stopTimeoutMillis = 5000, replayExpirationMillis = 60_000 // 1分钟后丢弃缓存 )
-
避免过度缓冲:
kotlin.shareIn( scope = ..., replay = 1, extraBufferCapacity = 1, // 总共缓冲2个值 onBufferOverflow = BufferOverflow.DROP_OLDEST )
九、测试策略
1. 测试 StateFlow
kotlin
@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
kotlin
@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 代码:
kotlin
class MyViewModel : ViewModel() {
private val _userName = MutableLiveData("")
val userName: LiveData<String> = _userName
fun updateName(name: String) {
_userName.value = name
}
}
迁移为 StateFlow:
kotlin
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 中收集:
kotlin
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.userName.collect { name ->
binding.textView.text = name
}
}
}
关键点:使用
repeatOnLifecycle
确保只在 UI 可见时收集,避免资源浪费
三、高级迁移模式
1. 数据流转换(替代 Transformations)
LiveData 方式:
kotlin
val userName: LiveData<String> = Transformations.map(_userName) {
"Hello, $it!"
}
Flow 方式:
kotlin
val userName: Flow<String> = _userName.map {
"Hello, $it!"
}
2. 多数据源合并(替代 MediatorLiveData)
LiveData 方式:
kotlin
val result = MediatorLiveData<String>().apply {
addSource(liveData1) { value = "$it + ${liveData2.value}" }
addSource(liveData2) { value = "${liveData1.value} + $it" }
}
Flow 方式:
kotlin
val result = combine(flow1, flow2) { data1, data2 ->
"$data1 + $data2"
}
四、处理一次性事件(替代 SingleLiveEvent)
LiveData 常被滥用处理一次性事件(如 Toast、导航),Flow 提供了更专业的解决方案:
使用 SharedFlow
kotlin
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 层收集:
kotlin
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 查询:
kotlin
@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun getUsers(): LiveData<List<User>>
}
迁移到 Flow:
kotlin
@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun getUsers(): Flow<List<User>>
}
ViewModel 中使用:
kotlin
val users: StateFlow<List<User>> = userDao.getUsers()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyList()
)
六、兼容现有代码的渐进式迁移
1. 使用 asLiveData()
临时兼容
kotlin
val userFlow: Flow<User> = repository.getUserFlow()
// 临时保持 LiveData 接口
val userLiveData: LiveData<User> = userFlow.asLiveData()
2. 混合使用策略
kotlin
class HybridViewModel : ViewModel() {
// 新功能使用 Flow
private val _newFeatureState = MutableStateFlow("")
val newFeatureState: StateFlow<String> = _newFeatureState.asStateFlow()
// 旧功能暂保持 LiveData
private val _oldFeatureData = MutableLiveData(0)
val oldFeatureData: LiveData<Int> = _oldFeatureData
}
七、测试策略调整
LiveData 测试:
kotlin
@Test
fun testLiveData() {
val liveData = MutableLiveData("test")
assertEquals("test", liveData.value)
}
Flow 测试:
kotlin
@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
共享流:kotlinval sharedFlow = someFlow .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = null )
-
避免重复创建 Flow:
kotlin// 错误方式 - 每次调用都创建新流 fun getUser() = userDao.getUserFlow() // 正确方式 - 共享同一个流 private val _userFlow = userDao.getUserFlow() val userFlow = _userFlow
-
合理选择背压策略:
kotlin// 缓冲最新值 val events = MutableSharedFlow<Event>( replay = 0, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST )
九、常见问题解决方案
Q1: 如何确保 Flow 收集不会泄漏?
kotlin
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
// 只有在此块内会激活收集
viewModel.data.collect { ... }
}
}
Q2: 为什么我的 Flow 不发射数据?
检查:
- Flow 是否被正确触发(冷流需要收集才会开始)
- 是否在正确的协程作用域内收集
- 是否有异常导致流终止
Q3: 如何处理 Java 代码调用?
kotlin
// 暴露 LiveData 给 Java 模块
val javaCompatData: LiveData<String> = flow.asLiveData()
十、完整迁移示例
迁移前 ViewModel:
kotlin
class OldViewModel : ViewModel() {
private val _data = MutableLiveData("")
val data: LiveData<String> = _data
private val _event = SingleLiveEvent<Event>()
val event: LiveData<Event> = _event
fun loadData() {
viewModelScope.launch {
_data.value = repository.fetchData()
_event.value = Event.ShowToast("Loaded")
}
}
}
迁移后 ViewModel:
kotlin
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 中添加:
kotlin
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
kotlin
class MyViewModel : ViewModel() {
// 私有可变的StateFlow
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
// 公开不可变的StateFlow
val 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.Loading
try {
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
kotlin
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 处理一次性事件
kotlin
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)
kotlin
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.)
kotlin
val numbers = MutableStateFlow(0)
val evenNumbers = numbers
.filter { it % 2 == 0 }
.map { "Even: $it" }
evenNumbers.collect { println(it) }
4. 性能优化
4.1 使用 stateIn 缓存 StateFlow
kotlin
val networkFlow = flow {
// 模拟网络请求
emit(repository.fetchData())
}
val cachedState = networkFlow.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000), // 5秒无订阅者停止
initialValue = "Loading..."
)
4.2 避免重复收集
kotlin
// 错误方式 - 每次重组都会创建新的收集器
@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 单元测试
kotlin
@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/Error
assertTrue(results[0] is MyViewModel.UiState.Loading)
job.cancel()
}
5.2 使用 Turbine 测试库
kotlin
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 示例:
kotlin
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.Loading
try {
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@launch
val 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 应用的强大工具,结合协程可以提供更简洁、更安全的状态管理方案。