一 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 應用的強大工具,結合協程可以提供更簡潔、更安全的狀態管理方案。