Kotlin Flow流

一 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)
典型用途 持续更新的状态(如用户积分) 一次性事件(如"购买成功"提示)
内存占用 始终保存最新值 按需缓存(可配置)
是否热流

五、为什么要用它们?

  1. 节省资源:避免重复计算(多个界面可以共享同一个数据源)

    • ❌ 不用时:每个界面都单独请求一次网络数据
    • ✅ 使用后:所有界面共享同一份网络数据
  2. 保持一致性:所有订阅者看到的数据完全相同

    • 比如用户头像更新后,所有界面立即同步
  3. 自动管理生命周期

    • 当Activity销毁时自动停止收集
    • 当配置变更(如屏幕旋转)时保持数据不丢失

六、生活场景类比

场景1:微信群(stateIn)

  • 群里最后一条消息就是当前状态(.value)
  • 新成员进群立刻能看到最后一条消息
  • 适合:工作群的状态同步

场景2:电台广播(shareIn)

  • 主播不断发送新消息
  • 听众打开收音机时:
    • 可以设置是否听之前的回放(replay)
    • 但无法直接问"刚才最后一首歌是什么"(无.value)
  • 适合:交通路况实时播报

七、什么时候用哪个?

用 stateIn 当:

  • 需要随时知道"当前值"
  • 数据会持续变化且需要被多个地方使用
  • 例如:
    • 用户登录状态
    • 购物车商品数量
    • 实时位置更新

用 shareIn 当:

  • 只关心新事件,不关心历史值
  • 事件可能被多个接收者处理
  • 例如:
    • "订单支付成功"通知
    • 错误提示消息
    • 页面跳转指令

八、超简单选择流程图

复制代码
要管理持续变化的状态吗?
   是 → 需要直接访问当前值吗?
      是 → 用 stateIn
      否 → 用 shareIn(replay=1)
   否 → 这是一次性事件吗?
      是 → 用 shareIn(replay=0)

记住这个简单的口诀:
"状态用state,事件用share,想要回放加replay"

二 Kotlin Flow 的 shareInstateIn 操作符完全指南

在 Kotlin Flow 的使用中,shareInstateIn 是两个关键的操作符,用于优化流的共享和状态管理。本教程将深入解析这两个操作符的使用场景、区别和最佳实践。

一、核心概念解析

1. 冷流 vs 热流

  • 冷流 (Cold Flow) :每个收集者都会触发独立的执行(如普通的 flow{} 构建器)
  • 热流 (Hot Flow) :数据发射独立于收集者存在(如 StateFlowSharedFlow

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:必须提供的初始值

特点:

  1. 总是有当前值(通过 value 属性访问)
  2. 新收集者立即获得最新值
  3. 适合表示 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

特点:

  1. 可以有多个订阅者
  2. 没有 value 属性,必须通过收集获取数据
  3. 适合事件处理(如 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)
    • 可配置 stopTimeoutMillisreplayExpirationMillis
  • 用例:大多数 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()
)

八、性能优化技巧

  1. 合理设置 replay

    • UI 状态:replay = 1(确保新订阅者立即获得状态)
    • 事件通知:replay = 0(避免重复处理旧事件)
  2. 使用 WhileSubscribed 的过期策略

    kotlin 复制代码
    started = SharingStarted.WhileSubscribed(
        stopTimeoutMillis = 5000,
        replayExpirationMillis = 60_000 // 1分钟后丢弃缓存
    )
  3. 避免过度缓冲

    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:需要预加载的全局数据

通过本教程,应该已经掌握了 shareInstateIn 的核心用法和高级技巧。正确使用这两个操作符可以显著提升应用的性能和资源利用率。

三 从 LiveData 迁移到 Kotlin Flow 完整教程

LiveData 长期以来是 Android 架构组件中状态管理的核心,但随着 Kotlin Flow 的成熟,Google 官方推荐将现有 LiveData 迁移到 Flow。本教程基于官方文章并扩展实践细节,完成平滑迁移。

一、为什么要从 LiveData 迁移到 Flow?

LiveData 的局限性

  1. 有限的运算符:只有简单的 map/switchMap 转换
  2. 线程限制:只能在主线程观察
  3. 一次性操作:不适合处理事件流
  4. 生命周期耦合:虽然方便但也限制了灵活性

Flow 的优势

  1. 丰富的操作符:filter, transform, combine 等 100+ 操作符
  2. 灵活的线程控制:通过 Dispatchers 指定执行线程
  3. 响应式编程:完整的事件流处理能力
  4. 协程集成:与 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()
}

八、性能优化技巧

  1. 使用 stateIn 共享流

    kotlin 复制代码
    val sharedFlow = someFlow
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = null
        )
  2. 避免重复创建 Flow

    kotlin 复制代码
    // 错误方式 - 每次调用都创建新流
    fun getUser() = userDao.getUserFlow()
    
    // 正确方式 - 共享同一个流
    private val _userFlow = userDao.getUserFlow()
    val userFlow = _userFlow
  3. 合理选择背压策略

    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 不发射数据?

检查:

  1. Flow 是否被正确触发(冷流需要收集才会开始)
  2. 是否在正确的协程作用域内收集
  3. 是否有异常导致流终止

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: 为什么我的收集器没有收到更新?

检查:

  1. 是否在正确的生命周期范围内收集
  2. Flow 是否有发射新值
  3. 是否在正确的协程上下文中

Q4: 如何避免内存泄漏?

使用 repeatOnLifecycleflowWithLifecycle 确保只在活跃生命周期收集。

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 应用的强大工具,结合协程可以提供更简洁、更安全的状态管理方案。

相关推荐
景天科技苑1 小时前
【Rust通用集合类型】Rust向量Vector、String、HashMap原理解析与应用实战
开发语言·后端·rust·vector·hashmap·string·rust通用集合类型
阿沁QWQ1 小时前
友元函数和友元类
开发语言·c++
lucky_tom2 小时前
【android Framework 探究】pixel 5 内核编译
android
小黑随笔3 小时前
【Golang玩转本地大模型实战(一):ollma部署模型及流式调用】
开发语言·后端·golang
江沉晚呤时3 小时前
Redis缓存穿透、缓存击穿与缓存雪崩:如何在.NET Core中解决
java·开发语言·后端·算法·spring·排序算法
achene_ql3 小时前
缓存置换:用c++实现最近最少使用(LRU)算法
开发语言·c++·算法·缓存
NO Exception?3 小时前
完美解决 mobile-ffmpeg Not overwriting - exiting
android·ffmpeg·pcm
高效匠人4 小时前
Python10天冲刺-设计模型之策略模式
开发语言·人工智能·python·策略模式
0wioiw04 小时前
安卓基础(点击项目)
android·windows
帅得不敢出门4 小时前
Android Framework学习二:Activity创建及View绘制流程
android·java·学习·framework·安卓·activity·window