網站首頁 編程語言 正文
前言
翻譯自:arkadiuszchmura.com/posts/be-ca…
最近我在負責一段代碼庫,需要在使用 Flow
的 Data 層和仍然依賴 LiveData
暴露 State 數據的 UI 層之間實現橋接。好在 androidx.lifecycle
框架已經提供了一個叫做 asLiveData()
的方法,可以讓你毫不費力地將 Flow
轉為 LiveData
。
然而使用這種方式得到的 LiveData 需要牢記一點:在擁有一個及以上活躍的觀察者的條件下,它才會發射數據。假使上游的 flow 產生了更新,但對應的 LiveData 并非活躍的狀態,那么它將無法獲得最新的數值。
讓我通過如下的實例,向你展示我們可能會遇到的這種潛在問題。
示例
我們有一個簡單的 Activity,它持有 AAC ViewModel
的實例:
class MainActivity : AppCompatActivity() { private val viewModel: MainViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } }
該 ViewModel
的實現是這樣的:
class MainViewModel : ViewModel() { private val repository = Repository() val state: LiveData<Int> = repository.state.asLiveData() }
它持有一個 Repository 實例,充當瑣碎的數據層。
同時 ViewModel
還通過前面提到的 asLiveData()
方法,將 Repository 持有的 StateFlow
轉為了 LiveData 并對外暴露了其 State 數據。
Repository 的實現如下:
class Repository { private val _state = MutableStateFlow(-1) val state: StateFlow<Int> = _state suspend fun update() { _state.emit(Random.nextInt(until = 1000)) } }
它擁有一個包裹著 Integer 數據(初始值為 -1)的 StateFlow
示例,同時對外提供了一個方法允許外界更新它的 State:從 0 到 1000 之間取得一個新的隨機數。
試想一下,假使希望 Activity 創建的時候就能執行這個數據更新。我們可以這么實現:
- 在
MainViewModel
內創建一個init()
來做這個操作 - Activity 的
onCreate()
里調用該方法
// MainViewModel fun init() { // update() is suspending, so we launch a new coroutine here viewModelScope.launch { repository.update() } } ? // MainActivity override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) viewModel.init() }
這樣的話,Activity 創建的時候一個新的協程將被啟動,最終會調用 Repository 的 update()
,生成一個隨機數并發射到它的 State。
此外,我們可能還需要在 ViewModel
中去發送包含了新生成數值的事件出去。可以在 ViewModel
中添加一個sendAnalyticalEvent()
,這樣可以在執行完 Repository 的 update()
之后立即調用它。
// MainViewModel fun init() { viewModelScope.launch { repository.update() sendAnalyticalEvent() // <-- NEW } } private fun sendAnalyticalEvent() { // Typically, we would schedule a network request here val liveDataValue = state.value val flowValue = repository.state.value Log.d("Current number in LiveData", "$liveDataValue") Log.d("Current number in StateFlow", "$flowValue") }
該方法內,我們可以做些典型的操作,比如向后端服務器發送網絡請求。這里,讓我們僅僅在 Logcat 里打印來自 LiveData
and Flow
的數值即可。
上面的運行結果相當出乎意料。你可能會爭辯道:LiveData
沒有獲取到最新的數值,是因為沒有足夠的時間從上游的 flow 中收集數據,不然的話肯定能夠拿到正確的數值。
但這個 case 里,不僅僅是 LiveData
獲得到的是錯誤的數值,它獲得到的是 null。而且請別忘了,它的存放在 Repository 里的初值是 -1。這只能代表一個意思:這里的 LiveData
壓根沒有從 StateFlow
里收集任何數據。
原因是我們還沒有開始觀察這個 LiveData
,它自然會被當作是非活躍的。而且根據 asLiveData()
方法的文檔可以知道,在這種情況下 LiveData
不會從上游的 flow 收集任何數據。
asLiveData:Creates a LiveData that has values collected from the origin Flow.
上游 flow 數據的收集發生在 LiveData
變成活躍的時候,即 LiveData.onActive
。如果 flow 尚未完成,而 LiveData
變成了非激活狀態,即 LiveData.onActive
,那么 flow 的數據收集將在timeoutInMs
參數指定的時間后被取消。除非在超時之前,LiveData
變成活躍狀態。
一旦我們開始在 Activity 里觀察 LiveData
的數據(因此將促使 LiveData 變成活躍狀態),它就能夠擁有正確的、最新的數值了。
// MainActivity override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) viewModel.init() viewModel.state.observe(this) { // <-- NEW Log.d("Current number in MainActivity", "$it") } }
如下是 Logcat 里新的輸出。
上面的示例里,我們采用的是 StateFlow
,但規則同樣適用于 SharedFlow
。
而且,情況將更加糟糕,因為當 LiveData
處于非激活狀態的時候,任何發送給 SharedFlow
的事件都將永久丟失(默認情況下 SharedFlow
不會將任何數值重新發送給新的訂閱者)。
總結
請時刻記住采用 asLiveData()
方法轉換 Flow
得到的 LiveData
將會和預期的稍稍不同:它只會在注冊了活躍觀察者的情況下發射數據。
就我個人而言,這種行為無可厚非:因為我們都還沒有觀察它、自然不會在意 LiveData
的數值是啥、能不能獲取得到。但話說回來,確實存在一些場景,需要在你尚未開始觀察的時候,去訪問 ViewModel
中 LiveData
的當前數值。
通過閱讀這篇文章,我希望你在遇到這種獲取不到正確數值的情況時,不要驚訝、心中有數。
原文鏈接:https://juejin.cn/post/7186249265138794551
相關推薦
- 2022-07-13 Android 動態獲取 Resource ID
- 2023-02-17 docker快速部署zabbix的方法_docker
- 2022-10-09 C#實現選擇排序_C#教程
- 2022-04-17 python讀取文件夾下所有文件
- 2022-04-12 如何解決:git push -u origin msster時出現error: failed to
- 2022-09-26 車載藍牙PIN碼是什么
- 2022-06-08 Spring Cloud Ribbon執行流程
- 2022-02-02 uni 修改數據頁面不重新渲染
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支