Flow 是一種基于流的編程模型,本文我們將向大家介紹響應(yīng)式編程以及其在 Android 開發(fā)中的實(shí)踐攀芯,您將了解到如何將生命周期屯断、旋轉(zhuǎn)及切換到后臺等狀態(tài)綁定到 Flow 中文虏,并且測試它們是否能按照預(yù)期執(zhí)行
使用 Kotlin Flow 構(gòu)建數(shù)據(jù)流 "管道"
單向數(shù)據(jù)流
加載數(shù)據(jù)流的過程
每款 Android 應(yīng)用都需要以某種方式收發(fā)數(shù)據(jù)侣诺,比如從數(shù)據(jù)庫獲取用戶名、從服務(wù)器加載文檔氧秘,以及對用戶進(jìn)行身份驗(yàn)證等年鸳。接下來,我們將介紹如何將數(shù)據(jù)加載到 Flow丸相,然后經(jīng)過轉(zhuǎn)換后暴露給視圖進(jìn)行展示搔确。
為了大家更方便地理解 Flow,我們以 Pancho (潘喬) 的故事來展開灭忠。當(dāng)住在山上的 Pancho 想從湖中獲取淡水時(shí)膳算,會像大多數(shù)新手一開始一樣,拿個(gè)水桶走到湖邊取水弛作,然后再走回來涕蜂。
山上的 Pancho但有時(shí) Pahcho 不走運(yùn),走到湖邊時(shí)發(fā)現(xiàn)湖水已經(jīng)干涸映琳,于是就不得不再去別處尋找水源机隙。發(fā)生了幾次這種情況后,Pancho 意識到萨西,搭建一些基礎(chǔ)設(shè)施可以解決這個(gè)問題有鹿。于是他在湖邊安裝了一些管道,當(dāng)湖中有水時(shí)谎脯,只用擰開水龍頭就能取到水葱跋。知道了如何安裝管道,就能很自然地想到從多個(gè)水源地把管道組合,這樣一來 Pancho 就不必再檢查湖水是否已經(jīng)干涸娱俺。
鋪設(shè)管道在 Android 應(yīng)用中您可以簡單地在每次需要時(shí)請求數(shù)據(jù)际看,例如我們可以使用掛起函數(shù)來實(shí)現(xiàn)在每次視圖啟動時(shí)向 ViewModel 請求數(shù)據(jù),而后 ViewModel 又向數(shù)據(jù)層請求數(shù)據(jù)矢否,接下來這一切又在相反的方向上發(fā)生仲闽。不過這樣過了一段時(shí)間之后,像 Pancho 這樣的開發(fā)者們往往會想到僵朗,其實(shí)有必要投入一些成本來構(gòu)建一些基礎(chǔ)設(shè)施赖欣,我們就可以不再請求數(shù)據(jù)而改為觀察數(shù)據(jù)。觀察數(shù)據(jù)就像安裝取水管道一樣验庙,部署完成后對數(shù)據(jù)源的任何更新都將自動向下流動到視圖中顶吮,Pancho 再也不用走到湖邊去了。
傳統(tǒng)的請求數(shù)據(jù)與單向數(shù)據(jù)流
響應(yīng)式編程
我們將這類觀察者會自動對被觀察者對象的變化而作出反應(yīng)的系統(tǒng)稱之為響應(yīng)式編程粪薛,它的另一個(gè)設(shè)計(jì)要點(diǎn)是保持?jǐn)?shù)據(jù)只在一個(gè)方向上流動悴了,因?yàn)檫@樣更容易管理且不易出錯(cuò)。某個(gè)示例應(yīng)用界面的 "數(shù)據(jù)流動" 如下圖所示违寿,身份認(rèn)證管理器會告訴數(shù)據(jù)庫用戶已登錄湃交,而數(shù)據(jù)庫又必須告訴遠(yuǎn)程數(shù)據(jù)源來加載一組不同的數(shù)據(jù);與此同時(shí)這些操作在獲取新數(shù)據(jù)時(shí)都會告訴視圖顯示一個(gè)轉(zhuǎn)圈的加載圖標(biāo)藤巢。對此我想說這雖然是可行的搞莺,但容易出現(xiàn)錯(cuò)誤。
錯(cuò)綜復(fù)雜的 "數(shù)據(jù)流動"
更好的方式則是讓數(shù)據(jù)只在一個(gè)方向上流動掂咒,并創(chuàng)建一些基礎(chǔ)設(shè)施 (像 Pancho 鋪設(shè)管道那樣) 來組合和轉(zhuǎn)換這些數(shù)據(jù)流才沧,這些管道可以隨著狀態(tài)的變化而修改,比如在用戶退出登錄時(shí)重新安裝管道绍刮。
單向數(shù)據(jù)綁定
使用 Flow
可以想象對于這些組合和轉(zhuǎn)換來說温圆,我們需要一個(gè)成熟的工具來完成這些操作。在本文中我們將使用 Kotlin Flow 來實(shí)現(xiàn)孩革。Flow 并不是唯一的數(shù)據(jù)流構(gòu)建器岁歉,不過得益于它是協(xié)程的一部分并且得到了很好的支持。我們剛才一直用作比喻的水流嫉戚,在協(xié)程庫里稱之為 Flow 類型刨裆,我們用泛形 T 來指代數(shù)據(jù)流承載的用戶數(shù)據(jù)或者頁面狀態(tài)等任何類型。
生產(chǎn)者和消費(fèi)者
生產(chǎn)者會將數(shù)據(jù) emit (發(fā)送) 到數(shù)據(jù)流中彬檀,而消費(fèi)者則從數(shù)據(jù)流中 collect (收集) 這些數(shù)據(jù)帆啃。在 Android 中數(shù)據(jù)源或存儲區(qū)通常是應(yīng)用數(shù)據(jù)的生產(chǎn)者;消費(fèi)者則是視圖窍帝,它會把數(shù)據(jù)顯示在屏幕上努潘。
大多數(shù)情況下您都無需自行創(chuàng)建數(shù)據(jù)流,因?yàn)閿?shù)據(jù)源中依賴的庫,例如 DataStore疯坤、Retrofit报慕、Room 或 WorkManager 等常見的庫都已經(jīng)與協(xié)程及 Flow 集成在一起了。這些庫就像是水壩压怠,它們使用 Flow 來提供數(shù)據(jù)眠冈,您無需了解數(shù)據(jù)是如何生成的,只需 "接入管道" 即可菌瘫。
提供 Flow 支持的庫
我們來看一個(gè) Room 的例子蜗顽。您可以通過導(dǎo)出指定類型的數(shù)據(jù)流來獲取數(shù)據(jù)庫中發(fā)生變更的通知。在本例中雨让,Room 庫是生產(chǎn)者雇盖,它會在每次查詢后發(fā)現(xiàn)有更新時(shí)發(fā)送內(nèi)容。
@DAO
interface CodelabsDAO {
@Query("SELECT * FROM codelabs")
fun getAllCodelabs(): Flow<List<Codelab>>
}
創(chuàng)建 Flow
如果您要自己創(chuàng)建數(shù)據(jù)流栖忠,有一些方案可供選擇崔挖,比如數(shù)據(jù)流構(gòu)建器。假設(shè)我們處于 UserMessagesDataSource 中庵寞,當(dāng)您希望頻繁地在應(yīng)用內(nèi)檢查新消息時(shí)狸相,可以將用戶消息暴露為消息列表類型的數(shù)據(jù)流。我們使用數(shù)據(jù)流構(gòu)建器來創(chuàng)建數(shù)據(jù)流皇帮,因?yàn)?Flow 是在協(xié)程上下文環(huán)境中運(yùn)行的卷哩,它以掛起代碼塊作為參數(shù)蛋辈,這也意味著它能夠調(diào)用掛起函數(shù)属拾,我們可以在代碼塊中使用 while(true) 來循環(huán)執(zhí)行我們的邏輯。
在示例代碼中冷溶,我們首先從 API 獲取消息渐白,然后使用 emit 掛起函數(shù)將結(jié)果添加到 Flow 中,這將掛起協(xié)程直到收集器接收到數(shù)據(jù)項(xiàng)逞频,最后我們將協(xié)程掛起一段時(shí)間纯衍。在 Flow 中,操作會在同一個(gè)協(xié)程中順序執(zhí)行苗胀,使用 while(true) 循環(huán)可以讓 Flow 持續(xù)獲取新消息直到觀察者停止收集數(shù)據(jù)襟诸。傳遞給數(shù)據(jù)流構(gòu)建器的掛起代碼塊通常被稱為 "生產(chǎn)者代碼塊"。
class UserMessagesDataSource(
private val messagesApi: MessagesApi,
private val refreshIntervalMs: Long = 5000
) {
val latestMessages: Floa<List<Message>> = flow {
white(true) {
val userMessages = messagesApi.fetchLatestMessages()
emit(userMessages) // 將結(jié)果發(fā)送給 Flow
delay(refreshIntervalMs) // ? 掛起一段時(shí)間
}
}
}
轉(zhuǎn)換 Flow
在 Android 中基协,生產(chǎn)者和消費(fèi)者之間的層可以使用中間運(yùn)算符修改數(shù)據(jù)流來適應(yīng)下一層的要求歌亲。
在本例中,我們將 latestMessages 流作為數(shù)據(jù)流的起點(diǎn)澜驮,則可以使用 map 運(yùn)算符將數(shù)據(jù)轉(zhuǎn)換為不同的類型陷揪,例如我們可以使用 map lambda 表達(dá)式將來自數(shù)據(jù)源的原始消息轉(zhuǎn)換為 MessagesUiModel,這一操作可以更好地抽象當(dāng)前層級,每個(gè)運(yùn)算符都應(yīng)根據(jù)其功能創(chuàng)建一個(gè)新的 Flow 來發(fā)送數(shù)據(jù)悍缠。我們還可以使用 filter 運(yùn)算符過濾數(shù)據(jù)流來獲得包含重要通知的數(shù)據(jù)流卦绣。而 catch 運(yùn)算符則可以捕獲上游數(shù)據(jù)流中發(fā)生的異常,上游數(shù)據(jù)流是指在生產(chǎn)者代碼塊和當(dāng)前運(yùn)算符之間調(diào)用的運(yùn)算符產(chǎn)生的數(shù)據(jù)流飞蚓,而在當(dāng)前運(yùn)算符之后生成的數(shù)據(jù)流則被稱為下游數(shù)據(jù)流滤港。catch 運(yùn)算符還可以在有需要的時(shí)候再次拋出異常或者發(fā)送新值趴拧,我們在示例代碼中可以看到其在捕獲到 IllegalArgumentExceptions 時(shí)將其重新拋出蜗搔,并且在發(fā)生其他異常時(shí)發(fā)送一個(gè)空列表:
val importantUserMessages: Flow<MessageUiModel> =
userMessageDataSource.latestMessages
.map { userMessage ->
userMessages.toUiModel()
}
.filter { messageUiModel ->
messagesUiModel.containsImportantNotifications()
}
.catch { e ->
analytics.log("Error loading reserved event")
if (e is IllegalArgumentException) throw e
else emit(emptyList())
}
收集 Flow
現(xiàn)在我們已經(jīng)了解過如何生成和修改數(shù)據(jù)流,接下來了解一下如何收集數(shù)據(jù)流八堡。收集數(shù)據(jù)流通常發(fā)生在視圖層樟凄,因?yàn)檫@是我們想要在屏幕上顯示數(shù)據(jù)的地方。
在本例中兄渺,我們希望列表中能夠顯示最新消息以便 Pancho 能夠了解最新動態(tài)缝龄。我們可以使用終端運(yùn)算符 collect 來監(jiān)聽數(shù)據(jù)流發(fā)送的所有值,collect 接收一個(gè)函數(shù)作為參數(shù)挂谍,每個(gè)新值都會調(diào)用該參數(shù)叔壤,并且由于它是一個(gè)掛起函數(shù),因此需要在協(xié)程中執(zhí)行口叙。
userMessages.collect { messages ->
listAdapter.submitList(messages)
}
在 Flow 中使用終端運(yùn)算符將按需創(chuàng)建數(shù)據(jù)流并開始發(fā)送值炼绘,而相反的是中間操作符只是設(shè)置了一個(gè)操作鏈,其會在數(shù)據(jù)被發(fā)送到數(shù)據(jù)流時(shí)延遲執(zhí)行妄田。每次對 userMessages 調(diào)用 collect 時(shí)都會創(chuàng)建一個(gè)新的數(shù)據(jù)流俺亮,其生產(chǎn)者代碼塊將根據(jù)自己的時(shí)間間隔開始刷新來自 API 的消息。在協(xié)程中我們將這種按需創(chuàng)建并且只有在被觀察時(shí)才會發(fā)送數(shù)據(jù)的數(shù)據(jù)流稱之為冷流 (Cold Stream)疟呐。
在 Android 視圖上收集數(shù)據(jù)流
在 Android 的視圖中收集數(shù)據(jù)流要注意兩點(diǎn)脚曾,第一是在后臺運(yùn)行時(shí)不應(yīng)浪費(fèi)資源,第二是配置變更启具。
安全收集
假設(shè)我們在 MessagesActivity 中本讥,如果希望在屏幕上顯示消息列表,則應(yīng)該當(dāng)界面沒有顯示在屏幕上時(shí)停止收集鲁冯,就像是 Pancho 在刷牙或者睡覺時(shí)應(yīng)該關(guān)上水龍頭一樣拷沸。我們有多種具有生命周期感知能力的方案,來實(shí)現(xiàn)當(dāng)信息不在屏幕上展示就不從數(shù)據(jù)流中收集信息的功能薯演,比如 androidx.lifecycle:lifecycle-runtime-ktx 包中的 Lifecycle.repeatOnLifecycle(state) 和 Flow<T>.flowWithLifecycle(lifecycle, state)撞芍。您還可以在 ViewModel 中使用 androidx.lifecycle:lifecycle-livedata-ktx 包里的 Flow<T>.asLiveData(): LiveData 將數(shù)據(jù)流轉(zhuǎn)換為 LiveData,這樣就可以像往常一樣使用 LiveData 來實(shí)現(xiàn)這件事情涣仿。不過為了簡單起見勤庐,這里推薦使用 repeatOnLifecycle 從界面層收集數(shù)據(jù)流示惊。
repeatOnLifecycle 是一個(gè)接收 Lifecycle.State 作為參數(shù)的掛起函數(shù),該 API 具有生命周期感知能力愉镰,所以能夠在當(dāng)生命周期進(jìn)入響應(yīng)狀態(tài)時(shí)自動使用傳遞給它的代碼塊啟動新的協(xié)程米罚,并且在生命周期離開該狀態(tài)時(shí)取消該協(xié)程。在上面的例子中丈探,我們使用了 Activity 的 lifecycleScope 來啟動協(xié)程录择,由于 repeatOnLifecycle 是掛起函數(shù),所以它需要在協(xié)程中被調(diào)用碗降。最佳實(shí)踐是在生命周期初始化時(shí)調(diào)用該函數(shù)隘竭,就像上面的例子中我們在 Activity 的 onCreate 中調(diào)用一樣:
import androidx.lifecycle.repeatOnLifecycle
class MessagesActivity : AppCompatActivity() {
val viewModel: MessagesViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED)
viewModel.userMessages.collect { messages ->
listAdapter.submitList(messages)
}
}
// 協(xié)程將會在 lifecycle 進(jìn)入 DESTROYED 后被恢復(fù)
}
}
}
repeatOnLifecycle 的可重啟行為充分考慮了界面的生命周期,不過需要注意的是讼渊,直到生命周期進(jìn)入 DESTROYED动看,調(diào)用 repeatOnLifecycle 的協(xié)程都不會恢復(fù)執(zhí)行,因此如果您需要從多個(gè)數(shù)據(jù)流中進(jìn)行收集爪幻,則應(yīng)在 repeatOnLifecycle 代碼塊內(nèi)多次使用 launch 來創(chuàng)建協(xié)程:
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
viewModel.userMessages.collect { … }
}
launch {
otherFlow.collect { … }
}
}
}
如果只需從一個(gè)數(shù)據(jù)流中進(jìn)行收集菱皆,則可使用 flowWithLifecycle 來收集數(shù)據(jù),它能夠在生命周期進(jìn)入目標(biāo)狀態(tài)時(shí)發(fā)送數(shù)據(jù)挨稿,并在離開目標(biāo)狀態(tài)時(shí)取消內(nèi)部的生產(chǎn)者:
lifecycleScope.launch {
viewModel.userMessages
.flowWithLifecycle(lifecycle, State.STARTED)
.collect { messages ->
listAdapter.submitList(messages)
}
}
為了能夠直觀地展示具體的運(yùn)作過程仇轻,我們來探索一下此 Activity 的生命周期,首先是創(chuàng)建完成并向用戶可見奶甘;接下來用戶按下了主屏幕按鈕將應(yīng)用退到后臺篷店,此時(shí) Activity 會收到 onStop 信號;當(dāng)重新打開應(yīng)用時(shí)又會調(diào)用 onStart臭家。如果您調(diào)用 repeatOnLifecycle 并傳入 STARTED 狀態(tài)疲陕,界面就只會在屏幕上顯示時(shí)收集數(shù)據(jù)流發(fā)出的信號,并且在應(yīng)用轉(zhuǎn)到后臺時(shí)取消收集侣监。
Activity 的生命周期
repeatOnLifecycle 和 flowWithLifecycle 是 lifecycle-runtime-ktx 庫在 2.4.0 穩(wěn)定版中新增的 API鸭轮,在沒有這些 API 之前您可能已經(jīng)以其他方式從 Android 界面中收集數(shù)據(jù)流,例如像上面的代碼一樣直接從 lifecycleScope.launch 啟動的協(xié)程中收集橄霉,雖然這樣看起來也能工作但不一定安全,因?yàn)檫@種方式將持續(xù)從數(shù)據(jù)流中收集數(shù)據(jù)并更新界面元素邑蒋,即便是應(yīng)用退出到后臺時(shí)也一樣姓蜂。如果使用 launchWhenStarted 替代它的話娱据,情況會稍微好一些烂完,因?yàn)樗鼤谔幱诤笈_時(shí)將收集掛起。但這樣會在讓數(shù)據(jù)流生產(chǎn)者保持活躍狀態(tài)缘眶,有可能會在后臺持續(xù)發(fā)出不需要在屏幕上顯示的數(shù)據(jù)項(xiàng)卿堂,從而將內(nèi)存占滿束莫。由于界面并不知道數(shù)據(jù)流生產(chǎn)者的實(shí)現(xiàn)方式懒棉,所以最好謹(jǐn)慎一些,使用 repeatOnLifecycle 或 flowWithLifecycle 來避免界面在處于后臺時(shí)收集數(shù)據(jù)或保持?jǐn)?shù)據(jù)流生產(chǎn)者處于活躍狀態(tài)览绿。下面是一段不安全的使用方式示例:
class MessagesActivity : AppCompatActivity() {
val viewModel: MessagesViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
// ? 危險(xiǎn)的操作
lifecycleScope.launch {
viewModel.userMessage.collect { messages ->
listAdapter.submitList(messages)
}
}
// ? 危險(xiǎn)的操作
LifecycleCoroutineScope.launchWhenX {
flow.collect { … }
}
}
}
配置變更
當(dāng)您向視圖暴露數(shù)據(jù)流時(shí)策严,必須要考慮到您正在嘗試在具有不同生命周期的兩個(gè)元素之間傳遞數(shù)據(jù),并不是所有生命周期都會出現(xiàn)問題饿敲,但在 Activity 和 Fragment 的生命周期里會比較棘手妻导。當(dāng)設(shè)備旋轉(zhuǎn)或者接收到配置變更時(shí),所有的 Activity 都可能會重啟但 ViewModel 卻能被保留怀各,因此您不能把任意數(shù)據(jù)流都簡單地從 ViewModel 中暴露出來倔韭。
旋轉(zhuǎn)屏幕會重建 Activity 但能夠保留 ViewModel
以如下代碼中的冷流為例,由于每次收集冷流時(shí)它都會重啟瓢对,所以在設(shè)備旋轉(zhuǎn)之后會再次調(diào)用 repository.fetchItem()寿酌。我們需要某種緩沖區(qū)機(jī)制來保障無論重新收集多少次都可以保持?jǐn)?shù)據(jù),并在多個(gè)收集器之間共享數(shù)據(jù)硕蛹,而 StateFlow 正是為了此用途而設(shè)計(jì)的份名。在我們的湖泊比喻中,StateFlow 就好比水箱妓美,即使沒有收集器它也能持有數(shù)據(jù)僵腺。因?yàn)樗梢远啻伪皇占阅軌蚍判牡貙⑵渑c Activity 或 Fragment 一起使用壶栋。
val result: Flow<Result<UiState>> = flow {
emit(repository.fetchItem())
}
您可以使用 StateFlow 的可變版本辰如,并隨時(shí)根據(jù)需要在協(xié)程中更新它的值,但這樣做可能不太符合響應(yīng)式編程的風(fēng)格贵试,如下代碼所示:
private val _myUiState = MutableStateFlow<MyUiState>()
val myUiState: StateFlow<MyUiState> = _myUiState
init {
viewModelScope.launch {
_muUiState.value = Result.Loading
_myUiState.value = repository.fetchStuff()
}
}
Pancho 會建議您將各種類型的數(shù)據(jù)流都轉(zhuǎn)換為 StateFlow 來改進(jìn)這個(gè)問題琉兜,這樣 StateFlow 將接收來自上游數(shù)據(jù)流的所有更新并存儲最新的值,并且收集器的數(shù)量可以是 0 至任意多個(gè)毙玻,因此非常適合與 ViewModel 一起使用豌蟋。當(dāng)然,除此之外還有一些其他類型的 Flow桑滩,但推薦您使用 StateFlow梧疲,因?yàn)槲覀兛梢詫λM(jìn)行非常精確的優(yōu)化。
將任意數(shù)據(jù)流轉(zhuǎn)換為 StateFlow要將數(shù)據(jù)流轉(zhuǎn)換為 StateFlow 可以使用 stateIn 運(yùn)算符运准,它需要傳入三個(gè)參數(shù): initinalValue幌氮、scope 及 started。其中 initialValue 是因?yàn)?StateFlow 必須有值胁澳;而協(xié)程 scope 則是用于控制何時(shí)開始共享该互,在上面的例子中我們使用了 viewModelScope;最后的 started 是個(gè)有趣的參數(shù)韭畸,我們后面會聊到 WhileSubscribed(5000) 的作用宇智,先看這部分的代碼:
val result: StateFlow<Result<UiState>> = someFlow
.stateIn(
initialValue = Result.Loading
scope = viewModelScope,
started = WhileSubscribed(5000),
)
我們來看看這兩個(gè)場景: 第一種場景是旋轉(zhuǎn)蔓搞,在該場景中 Activity (也就是數(shù)據(jù)流收集器) 在短時(shí)間內(nèi)被銷毀然后重建;第二個(gè)場景是回到主屏幕随橘,這將會使我們的應(yīng)用進(jìn)入后臺喂分。在旋轉(zhuǎn)場景中我們不希望重啟任何數(shù)據(jù)流以便盡可能快地完成過渡,而在回到主屏幕的場景中我們則希望停止所有數(shù)據(jù)流以便節(jié)省電量和其他資源太防。
我們可以通過設(shè)置超時(shí)時(shí)間來正確判斷不同的場景妻顶,當(dāng)停止收集 StateFlow 時(shí),不會立即停止所有上游數(shù)據(jù)流蜒车,而是會等待一段時(shí)間讳嘱,如果在超時(shí)前再次收集數(shù)據(jù)則不會取消上游數(shù)據(jù)流,這就是 WhileSubscribed(5000) 的作用酿愧。當(dāng)設(shè)置了超時(shí)時(shí)間后沥潭,如果按下主屏幕按鈕會讓視圖立即結(jié)束收集,但 StateFlow 會經(jīng)過我們設(shè)置的超時(shí)時(shí)間之后才會停止其上游數(shù)據(jù)流嬉挡,如果用戶再次打開應(yīng)用則會自動重啟上游數(shù)據(jù)流钝鸽。而在旋轉(zhuǎn)場景中視圖只停止了很短的時(shí)間,無論如何都不會超過 5 秒鐘庞钢,因此 StateFlow 并不會重啟拔恰,所有的上游數(shù)據(jù)流都將會保持在活躍狀態(tài),就像什么都沒有發(fā)生一樣可以做到即時(shí)向用戶呈現(xiàn)旋轉(zhuǎn)后的屏幕基括。
設(shè)置超時(shí)時(shí)間來應(yīng)對不同的場景總的來說颜懊,建議您使用 StateFlow 來通過 ViewModel 暴露數(shù)據(jù)流,或者使用 asLiveData 來實(shí)現(xiàn)同樣的目的风皿,關(guān)于 StateFlow 或其父類 SharedFlow 的更多詳細(xì)信息河爹,請參閱: StateFlow 和 SharedFlow
測試數(shù)據(jù)流
測試數(shù)據(jù)流可能會比較復(fù)雜,因?yàn)橐幚淼膶ο笫橇魇綌?shù)據(jù)桐款,這里介紹在兩個(gè)不同的場景中有用的小技巧:首先是第一個(gè)場景咸这,被測單元依賴了數(shù)據(jù)流,那對此類場景進(jìn)行測試最簡單的方法就是用模擬生產(chǎn)者替代依賴項(xiàng)魔眨。在本例中媳维,您可以對這個(gè)模擬源進(jìn)行編程以對不同的測試用例發(fā)送其所需要的內(nèi)容。您可以像上面的例子一樣實(shí)現(xiàn)一個(gè)簡單的冷流冰沙,測試本身會對受測對象的輸出進(jìn)行斷言侨艾,輸出的內(nèi)容可以是數(shù)據(jù)流或其他任何類型。
被測單元依賴數(shù)據(jù)流的測試技巧模擬被測單元所依賴的數(shù)據(jù)流:
class MyFakeRepository : MyRepository {
fun observeCount() = flow {
emit(ITEM_1)
}
}
如果受測單元暴露一個(gè)數(shù)據(jù)流拓挥,并且您希望驗(yàn)證該值或一系列值,那么您可以通過多種方式收集它們袋励。您可以對數(shù)據(jù)流調(diào)用 first() 方法以進(jìn)行收集并在接收到第一個(gè)數(shù)據(jù)項(xiàng)后停止收集侥啤。您還可以調(diào)用 take(5) 并使用 toList 終端操作符來收集恰好 5 條消息当叭,這種方法可能非常有幫助。
測試數(shù)據(jù)流的技巧測試數(shù)據(jù)流:
@Test
fun myTest() = runBlocking {
// 收集第一個(gè)數(shù)據(jù)然后停止收集
val firstItem = repository.counter.first()
// 收集恰好 5 條消息
val first = repository.messages.take(5).toList()
}
回顧
感謝閱讀本文盖灸,希望您通過本文內(nèi)容已經(jīng)了解到為什么響應(yīng)式架構(gòu)值得投資蚁鳖,以及如何使用 Kotlin Flow 構(gòu)建您的基礎(chǔ)設(shè)施。文末提供了有關(guān)這方面的資料赁炎,包括涵蓋基礎(chǔ)知識的指南以及深入探討某些主題的文章醉箕。另外您還可以通過 Google I/O 應(yīng)用了解這些內(nèi)容的詳細(xì)信息,我們在早些時(shí)候?yàn)槠涓铝撕芏嘤嘘P(guān)數(shù)據(jù)流的內(nèi)容徙垫。
最后
給大家分享一份谷歌開源的《史上最詳Android版kotlin協(xié)程入門進(jìn)階實(shí)戰(zhàn)指南》讥裤,希望可以幫助大家用最短時(shí)間學(xué)習(xí) Kotlin攜程。教程通俗易懂姻报,實(shí)例豐富己英,既有基礎(chǔ)知識,也有進(jìn)階技能吴旋,能夠幫助讀者快速入門進(jìn)階损肛,是你學(xué)習(xí)Kotlin的葵花寶典,快收藏起來H偕治拿!需要的朋友可以點(diǎn)擊這里免費(fèi)領(lǐng)取。
第一章 Kotlin協(xié)程的基礎(chǔ)介紹
- 協(xié)程是什么
- 什么是Job 笆焰、Deferred 劫谅、協(xié)程作用域
- Kotlin協(xié)程的基礎(chǔ)用法
第二章 kotlin協(xié)程的關(guān)鍵知識點(diǎn)初步講解
- 協(xié)程調(diào)度器
- 協(xié)程調(diào)度器
- 協(xié)程啟動模式
- 協(xié)程作用域
- 掛起函數(shù)
第三章 kotlin協(xié)程的異常處理
- 協(xié)程異常的產(chǎn)生流程
- 協(xié)程的異常處理
第四章 kotlin協(xié)程在Android中的基礎(chǔ)應(yīng)用
- Android使用kotlin協(xié)程
- 在Activity與Framgent中使用協(xié)程
- ViewModel中使用協(xié)程
- 其他環(huán)境下使用協(xié)程
第五章 kotlin協(xié)程的網(wǎng)絡(luò)請求封裝
- 協(xié)程的常用環(huán)境
- 協(xié)程在網(wǎng)絡(luò)請求下的封裝及使用
- 高階函數(shù)方式
- 多狀態(tài)函數(shù)返回值方式
- 直接返回值的方式
第六章 深入kotlin協(xié)程原理(一)
- suspend 的花花腸子
- 藏在身后的- Continuation
- 村里的希望- SuspendLambda
第七章 深入kotlin協(xié)程原理(二)
- 協(xié)程的那些小秘密
- 協(xié)程的創(chuàng)建過程
- 協(xié)程的掛起與恢復(fù)
- 協(xié)程的執(zhí)行與狀態(tài)機(jī)
第八章 Kotlin Jetpack 實(shí)戰(zhàn)
- 從一個(gè)膜拜大神的 Demo 開始
- Kotlin 寫 Gradle 腳本是一種什么體驗(yàn)?
- Kotlin 編程的三重境界
- Kotlin 高階函數(shù)
- Kotlin 泛型
-Kotlin 擴(kuò)展 - Kotlin 委托
- 協(xié)程“不為人知”的調(diào)試技巧
- 圖解協(xié)程原理
第九章 Kotlin + 協(xié)程 + Retrofit + MVVM優(yōu)雅的實(shí)現(xiàn)網(wǎng)絡(luò)請求
- 項(xiàng)目配置
- 實(shí)現(xiàn)思路
- 協(xié)程實(shí)現(xiàn)
- 協(xié)程 + ViewModel + LiveData實(shí)現(xiàn)
- 后續(xù)優(yōu)化
- 異常處理
- 更新Retrofit 2.6.0
文章篇幅有限仙辟,內(nèi)容比較多同波,需要這份谷歌開源的《史上最詳Android版kotlin協(xié)程入門進(jìn)階實(shí)戰(zhàn)指南》完整版的朋友,點(diǎn)擊這里查看全部內(nèi)容保證100%免費(fèi)叠国。