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)
典型用途持續更新的狀態(如用戶積分)一次性事件(如"購買成功"提示)
內存占用始終保存最新值按需緩存(可配置)
是否熱流

五、為什么要用它們?

  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 操作符詳解

基本用法

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 狀態

使用場景示例:

用戶個人信息狀態管理

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

特點:

  1. 可以有多個訂閱者
  2. 沒有 value 屬性,必須通過收集獲取數據
  3. 適合事件處理(如 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)
    • 可配置 stopTimeoutMillisreplayExpirationMillis
  • 用例:大多數 UI 相關狀態
// 保留5秒供可能的重新訂閱
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000)

五、關鍵區別對比

特性stateInshareIn
返回類型StateFlowSharedFlow
初始值必須提供無要求
新收集者獲取立即獲得最新 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()
)

八、性能優化技巧

  1. 合理設置 replay

    • UI 狀態:replay = 1(確保新訂閱者立即獲得狀態)
    • 事件通知:replay = 0(避免重復處理舊事件)
  2. 使用 WhileSubscribed 的過期策略

    started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000,replayExpirationMillis = 60_000 // 1分鐘后丟棄緩存
    )
    
  3. 避免過度緩沖

    .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:需要預加載的全局數據

通過本教程,應該已經掌握了 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 代碼

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()
}

八、性能優化技巧

  1. 使用 stateIn 共享流

    val sharedFlow = someFlow.stateIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(5000),initialValue = null)
    
  2. 避免重復創建 Flow

    // 錯誤方式 - 每次調用都創建新流
    fun getUser() = userDao.getUserFlow()// 正確方式 - 共享同一個流
    private val _userFlow = userDao.getUserFlow()
    val userFlow = _userFlow
    
  3. 合理選擇背壓策略

    // 緩沖最新值
    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 不發射數據?

檢查:

  1. Flow 是否被正確觸發(冷流需要收集才會開始)
  2. 是否在正確的協程作用域內收集
  3. 是否有異常導致流終止

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

特性StateFlowLiveData
生命周期感知否(需配合 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: 為什么我的收集器沒有收到更新?

檢查:

  1. 是否在正確的生命周期范圍內收集
  2. Flow 是否有發射新值
  3. 是否在正確的協程上下文中

Q4: 如何避免內存泄漏?

使用 repeatOnLifecycleflowWithLifecycle 確保只在活躍生命周期收集。

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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/903924.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/903924.shtml
英文地址,請注明出處:http://en.pswp.cn/news/903924.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【嵌入式Linux】基于ARM-Linux的zero2平臺的智慧樓宇管理系統項目

目錄 1. 需求及項目準備&#xff08;此項目對于虛擬機和香橙派的配置基于上一個垃圾分類項目&#xff0c;如初次開發&#xff0c;兩個平臺的環境變量&#xff0c;阿里云接入&#xff0c;攝像頭配置可參考垃圾分類項目&#xff09;1.1 系統框圖1.2 硬件接線1.3 語音模塊配置1.4 …

Linux運維中常用的磁盤監控方式

在Linux運維中&#xff0c;磁盤監控是一項關鍵任務&#xff0c;因為它能幫助我們預防磁盤空間不足或性能問題導致的服務中斷或數據丟失。讓我們來看看有哪些常用的磁盤監控方法吧&#xff01; 1. 查看磁盤使用情況&#xff08;df命令&#xff09; df命令用于顯示文件系統的…

OpenCV第6課 圖像處理之幾何變換(縮放)

1.簡述 圖像幾何變換又稱為圖像空間變換,它將一幅圖像中的坐標位置映射到另一幅圖像中的新坐標位置。幾何變換并不改變圖像的像素值,只是在圖像平面上進行像素的重新安排。 根據OpenCV函數的不同,本節課將映射關系劃分為縮放、翻轉、仿射變換、透視等。 2.縮放 2.1 函數…

(35)VTK C++開發示例 ---將圖片映射到平面2

文章目錄 1. 概述2. CMake鏈接VTK3. main.cpp文件4. 演示效果 更多精彩內容&#x1f449;內容導航 &#x1f448;&#x1f449;VTK開發 &#x1f448; 1. 概述 與上一個示例不同的是&#xff0c;使用vtkImageReader2Factory根據文件擴展名或內容自動創建對應的圖像文件讀取器&a…

【模型量化】量化基礎

目錄 一、認識量化 二、量化基礎原理 2.1 對稱量化和非對稱量化 2.1.1 對稱量化 2.1.2 非對稱量化 2.1.3 量化后的矩陣乘 2.2 神經網絡量化 2.2.1 動態量化 2.2.2 靜態量化 2.3 量化感知訓練 一、認識量化 量化的主要目的是節約顯存、提高計算效率以及加快通信 dee…

【零基礎入門】一篇掌握Python中的字典(創建、訪問、修改、字典方法)【詳細版】

?? 個人主頁:十二月的貓-CSDN博客 ?? 系列專欄: ??《PyTorch科研加速指南:即插即用式模塊開發》-CSDN博客 ???? 十二月的寒冬阻擋不了春天的腳步,十二點的黑夜遮蔽不住黎明的曙光 目錄 1. 前言 2. 字典 2.1 字典的創建 2.1.1 大括號+直接賦值 2.1.2 大括號…

PHP-session

PHP中&#xff0c;session&#xff08;會話&#xff09;是一種在服務器上存儲用戶數據的方法&#xff0c;這些數據可以在多個頁面請求或訪問之間保持。Session提供了一種方式來跟蹤用戶狀態&#xff0c;比如登錄信息、購物車內容等。當用戶首次訪問網站時&#xff0c;服務器會創…

第 5 篇:紅黑樹:工程實踐中的平衡大師

上一篇我們探討了為何有序表需要“平衡”機制來保證 O(log N) 的穩定性能。現在&#xff0c;我們要認識一位在實際工程中應用最廣泛、久經考驗的“平衡大師”——紅黑樹 (Red-Black Tree)。 如果你用過 Java 的 TreeMap? 或 TreeSet?&#xff0c;或者 C STL 中的 map? 或 s…

第十六屆藍橋杯 2025 C/C++組 客流量上限

目錄 題目&#xff1a; 題目描述&#xff1a; 題目鏈接&#xff1a; 思路&#xff1a; 打表找規律&#xff1a; 核心思路&#xff1a; 思路詳解&#xff1a; 得到答案的方式&#xff1a; 按計算器&#xff1a; 暴力求解代碼&#xff1a; 快速冪代碼&#xff1a; 位運…

一天學完JDBC!!(萬字總結)

文章目錄 JDBC是什么 1、環境搭建 && 入門案例2、核心API理解①、注冊驅動(Driver類)②、Connection③、statement(sql注入)④、PreparedStatement⑤、ResultSet 3、jdbc擴展(ORM、批量操作)①、實體類和ORM②、批量操作 4. 連接池①、常用連接池②、Durid連接池③、Hi…

從原理到實戰講解回歸算法!!!

哈嘍&#xff0c;大家好&#xff0c;我是我不是小upper, 今天系統梳理了線性回歸的核心知識&#xff0c;從模型的基本原理、參數估計方法&#xff0c;到模型評估指標與實際應用場景&#xff0c;幫助大家深入理解這一經典的機器學習算法&#xff0c;助力數據分析與預測工作。 …

【dify—10】工作流實戰——文生圖工具

目錄 一、創建工作流 應用 二、安裝硅基流動 三、配置硅基流動 四、API測試 &#xff08;1&#xff09;進入API文檔 &#xff08;2&#xff09;復制curl代碼 &#xff08;3&#xff09;Postman測試API 五、 建立文生圖工作流 &#xff08;1&#xff09;建立http請求 &…

Rust將結構導出到json如何處理小數點問題

簡述 標準的 serde_json 序列化器不支持直接對浮點數進行格式化限制。如果將浮點數轉換成字符串&#xff0c;又太low逼。這里重點推薦rust_decimal。 #[derive(Serialize)] pub struct StockTickRow {datetime: NaiveDateTime,code: String,name: String,#[serde(serialize_w…

openEuler 22.03 安裝 Redis 6.2.9,支持離線安裝

目錄 一、環境檢查1.1 必要環境檢查1.2 在線安裝&#xff08;有網絡&#xff09;1.3 離線安裝&#xff08;無網絡&#xff09; 二、下載Redis2.1 在線下載2.2 離線下載 三、安裝Redis四、配置Redis服務五、開機自啟服務六、開放防火墻端口七、常用命令 一、環境檢查 1.1 必要環…

MySQL基本查詢(二)

文章目錄 UpdateDelete插入查詢結果&#xff08;select insert&#xff09;聚合函數分組聚合統計 Update 1. 語法&#xff1a; set后面加列屬性或者表達式 UPDATE table_name SET column expr [, column expr …][WHERE …] [ORDER BY …] [LIMIT …] 案例 將孫悟空同學的…

Android Framework學習二:Activity創建及View繪制流程

文章目錄 Window繪制流程Window Manager Service&#xff08;WMS&#xff09;SurfaceSurfaceFlinger 安卓View層次結構ActivityPhoneWindowActivity與PhoneWindow兩者之間的關系ViewRootImplDecorViewDecorView 的作用DecorView 的結構總結 Activity創建流程View invalidate調用…

基于ssm的智慧養老平臺(全套)

一、系統架構 前端&#xff1a;jsp | js | jquery | css 后端&#xff1a;spring | springmvc | mybatis 環境&#xff1a;jdk1.8 | mysql | maven | tomcat 二、代碼及數據庫 三、功能介紹 01. 登錄 02. 管理員-主頁 03. 管理員-個人中心 04. 管理員-…

計算機視覺技術的發展歷程

計算機視覺技術的發展歷程可以分為以下幾個階段&#xff1a; 早期探索階段&#xff08;1960s-1980s&#xff09; 1960年代&#xff1a;計算機視覺的概念開始形成&#xff0c;研究者嘗試讓計算機識別和理解圖像&#xff0c;主要集中在基礎的圖像處理&#xff0c;如邊緣檢測和特…

2025五一杯B題五一杯數學建模思路代碼文章教學: 礦山數據處理問題

完整內容請看文章最下面的推廣群 問題1. 根據附件1中的數據和&#xff0c;建立數學模型&#xff0c;對數據A進行某種變換&#xff0c;使得變換后的結果與數據盡可能接近。計算變換后的結果與數據的誤差&#xff0c;并分析誤差的來源&#xff08;如數據噪聲、模型偏差等&#xf…

.NET 平臺詳解

什么是 .NET&#xff1f; .NET 是一個由微軟開發的跨平臺、開源的開發者平臺&#xff0c;用于構建多種類型的應用程序。它提供了一致的編程模型和豐富的類庫&#xff0c;支持多種編程語言&#xff08;如 C#、F#、Visual Basic&#xff09;。 .NET 的核心組成 運行時環境 CLR …