網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
前言
翻譯自:arkadiuszchmura.com/posts/be-ca…
最近我在負(fù)責(zé)一段代碼庫(kù),需要在使用 Flow
的 Data 層和仍然依賴 LiveData
暴露 State 數(shù)據(jù)的 UI 層之間實(shí)現(xiàn)橋接。好在 androidx.lifecycle
框架已經(jīng)提供了一個(gè)叫做 asLiveData()
的方法,可以讓你毫不費(fèi)力地將 Flow
轉(zhuǎn)為 LiveData
。
然而使用這種方式得到的 LiveData 需要牢記一點(diǎn):在擁有一個(gè)及以上活躍的觀察者的條件下,它才會(huì)發(fā)射數(shù)據(jù)。假使上游的 flow 產(chǎn)生了更新,但對(duì)應(yīng)的 LiveData 并非活躍的狀態(tài),那么它將無(wú)法獲得最新的數(shù)值。
讓我通過(guò)如下的實(shí)例,向你展示我們可能會(huì)遇到的這種潛在問(wèn)題。
示例
我們有一個(gè)簡(jiǎn)單的 Activity,它持有 AAC ViewModel
的實(shí)例:
class MainActivity : AppCompatActivity() { private val viewModel: MainViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } }
該 ViewModel
的實(shí)現(xiàn)是這樣的:
class MainViewModel : ViewModel() { private val repository = Repository() val state: LiveData<Int> = repository.state.asLiveData() }
它持有一個(gè) Repository 實(shí)例,充當(dāng)瑣碎的數(shù)據(jù)層。
同時(shí) ViewModel
還通過(guò)前面提到的 asLiveData()
方法,將 Repository 持有的 StateFlow
轉(zhuǎn)為了 LiveData 并對(duì)外暴露了其 State 數(shù)據(jù)。
Repository 的實(shí)現(xiàn)如下:
class Repository { private val _state = MutableStateFlow(-1) val state: StateFlow<Int> = _state suspend fun update() { _state.emit(Random.nextInt(until = 1000)) } }
它擁有一個(gè)包裹著 Integer 數(shù)據(jù)(初始值為 -1)的 StateFlow
示例,同時(shí)對(duì)外提供了一個(gè)方法允許外界更新它的 State:從 0 到 1000 之間取得一個(gè)新的隨機(jī)數(shù)。
試想一下,假使希望 Activity 創(chuàng)建的時(shí)候就能執(zhí)行這個(gè)數(shù)據(jù)更新。我們可以這么實(shí)現(xiàn):
- 在
MainViewModel
內(nèi)創(chuàng)建一個(gè)init()
來(lái)做這個(gè)操作 - Activity 的
onCreate()
里調(diào)用該方法
// 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 創(chuàng)建的時(shí)候一個(gè)新的協(xié)程將被啟動(dòng),最終會(huì)調(diào)用 Repository 的 update()
,生成一個(gè)隨機(jī)數(shù)并發(fā)射到它的 State。
此外,我們可能還需要在 ViewModel
中去發(fā)送包含了新生成數(shù)值的事件出去。可以在 ViewModel
中添加一個(gè)sendAnalyticalEvent()
,這樣可以在執(zhí)行完 Repository 的 update()
之后立即調(diào)用它。
// 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") }
該方法內(nèi),我們可以做些典型的操作,比如向后端服務(wù)器發(fā)送網(wǎng)絡(luò)請(qǐng)求。這里,讓我們僅僅在 Logcat 里打印來(lái)自 LiveData
and Flow
的數(shù)值即可。
上面的運(yùn)行結(jié)果相當(dāng)出乎意料。你可能會(huì)爭(zhēng)辯道:LiveData
沒(méi)有獲取到最新的數(shù)值,是因?yàn)闆](méi)有足夠的時(shí)間從上游的 flow 中收集數(shù)據(jù),不然的話肯定能夠拿到正確的數(shù)值。
但這個(gè) case 里,不僅僅是 LiveData
獲得到的是錯(cuò)誤的數(shù)值,它獲得到的是 null。而且請(qǐng)別忘了,它的存放在 Repository 里的初值是 -1。這只能代表一個(gè)意思:這里的 LiveData
壓根沒(méi)有從 StateFlow
里收集任何數(shù)據(jù)。
原因是我們還沒(méi)有開始觀察這個(gè) LiveData
,它自然會(huì)被當(dāng)作是非活躍的。而且根據(jù) asLiveData()
方法的文檔可以知道,在這種情況下 LiveData
不會(huì)從上游的 flow 收集任何數(shù)據(jù)。
asLiveData:Creates a LiveData that has values collected from the origin Flow.
上游 flow 數(shù)據(jù)的收集發(fā)生在 LiveData
變成活躍的時(shí)候,即 LiveData.onActive
。如果 flow 尚未完成,而 LiveData
變成了非激活狀態(tài),即 LiveData.onActive
,那么 flow 的數(shù)據(jù)收集將在timeoutInMs
參數(shù)指定的時(shí)間后被取消。除非在超時(shí)之前,LiveData
變成活躍狀態(tài)。
一旦我們開始在 Activity 里觀察 LiveData
的數(shù)據(jù)(因此將促使 LiveData 變成活躍狀態(tài)),它就能夠擁有正確的、最新的數(shù)值了。
// 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
,但規(guī)則同樣適用于 SharedFlow
。
而且,情況將更加糟糕,因?yàn)楫?dāng) LiveData
處于非激活狀態(tài)的時(shí)候,任何發(fā)送給 SharedFlow
的事件都將永久丟失(默認(rèn)情況下 SharedFlow
不會(huì)將任何數(shù)值重新發(fā)送給新的訂閱者)。
總結(jié)
請(qǐng)時(shí)刻記住采用 asLiveData()
方法轉(zhuǎn)換 Flow
得到的 LiveData
將會(huì)和預(yù)期的稍稍不同:它只會(huì)在注冊(cè)了活躍觀察者的情況下發(fā)射數(shù)據(jù)。
就我個(gè)人而言,這種行為無(wú)可厚非:因?yàn)槲覀兌歼€沒(méi)有觀察它、自然不會(huì)在意 LiveData
的數(shù)值是啥、能不能獲取得到。但話說(shuō)回來(lái),確實(shí)存在一些場(chǎng)景,需要在你尚未開始觀察的時(shí)候,去訪問(wèn) ViewModel
中 LiveData
的當(dāng)前數(shù)值。
通過(guò)閱讀這篇文章,我希望你在遇到這種獲取不到正確數(shù)值的情況時(shí),不要驚訝、心中有數(shù)。
原文鏈接:https://juejin.cn/post/7186249265138794551
相關(guān)推薦
- 2022-07-24 C語(yǔ)言超詳細(xì)i講解雙向鏈表_C 語(yǔ)言
- 2022-01-16 npm ERR! code ELIFECYCLE npm ERR! errno 1 npm ERR!
- 2022-01-14 2022年了--你還不會(huì)手寫promise? 完成promise的所有實(shí)現(xiàn)
- 2022-07-26 Python中迭代器與生成器的用法_python
- 2022-03-03 iview 在 Table 組件中,文字過(guò)長(zhǎng)用省略號(hào)代替,鼠標(biāo)放上去 Tooltip 文字提示
- 2022-03-20 C++線程池實(shí)現(xiàn)代碼_C 語(yǔ)言
- 2022-09-08 Go語(yǔ)言中并發(fā)的工作原理_Golang
- 2022-08-31 pandas基礎(chǔ)?Series與Dataframe與numpy對(duì)二進(jìn)制文件輸入輸出_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支