kotlin-筆記02-Going with the flow - Kotlin Vocabulary

相關詞匯

Suspend functions :掛起函數(shù)。告訴kotlin編譯器,這個方法需要執(zhí)行在一個協(xié)程中
Coroutine:協(xié)程
Producer:提供者
Intermediary :(中介-可選option)中間的 可以修改發(fā)送到數(shù)據(jù)流的值,或修正數(shù)據(jù)流本身。
Consumer:消費者(使用者)使用數(shù)據(jù)流中的值
entity:實體(Java been)
emit():發(fā)射數(shù)據(jù)項
context:???????上下文web后端開發(fā)或UI線程的context;如需更改數(shù)據(jù)流的 CoroutineContext,請使用中間運算符 flowOn

Flow:流,用于存放異步存放element數(shù)據(jù) 官方文檔Android 上的 Kotlin 數(shù)據(jù)流
collect():收集
map{...} :中間層操作符 ,注意中間運算符可以接連應用,形成鏈式運算,在數(shù)據(jù)項被發(fā)送到數(shù)據(jù)流時延遲執(zhí)行。請注意,僅將一個中間運算符應用于數(shù)據(jù)流不會啟動數(shù)據(jù)流收集。
其他中間層操作符:除非使用其他中間運算符指定流,否則數(shù)據(jù)流始終為冷數(shù)據(jù)并延遲執(zhí)行。


youtube視頻鏈接

演講人 :MANUEL VICENTE VIVO
協(xié)程中的掛起函數(shù)用于一次調(diào)用,可以返回一個結果。但是,如何創(chuàng)建一個數(shù)據(jù)流來隨時間返回多個結果呢?在這個Kotlin詞匯的視頻中,Manuel會給我們帶來Kotlin Flow的好處,還有更多!
Going with the flow - Kotlin Vocabulary

推薦另外一個協(xié)程視頻The ABC of Coroutines - Kotlin Vocabulary

文章

image.png

android上的kotlin數(shù)據(jù)流Flow--android開發(fā)者


介紹

Suspend functions in Coroutines are meant for one-shot calls that could return a result.
協(xié)程中的掛起函數(shù)用于一次調(diào)用,可以返回一個結果。
But how could you create a stream of data or return multiple results over time?With Flow.
但是如何創(chuàng)建數(shù)據(jù)流或隨時間返回多個結果呢? 使用Flow.

Yes,you nailed ti.
This video builds up on "The ABC of Coroutines"episode.
If you're a beginner,I would recommend watching that one first ,as today we are going with the Flow.
如果你是初學者,我建議你先看這部電影,因為今天我們要講的是Flow“流”。
We'll cover the differences between suspend functions and Flow, the different entities involved in streams of data,and all the async possibilities that unfold when you use this powerful API.

本視頻我們將討論suspend函數(shù)和Flow之間的區(qū)別、不同的entity涉及stream流數(shù)據(jù) ,以及在使用這個強大的API時出現(xiàn)的所有異步可能性。
If you learn something new ,like the video and subscribe to the channel, but only if you think we've earned it.
如果你學到了新東西,喜歡這個視頻并訂閱這個頻道,但前提是你認為這是我們應得的。

In Coroutines ,a Flow is a type that can emit multiple values sequentially,as opposed to suspend functions that return only a singgle value.

在協(xié)程中,F(xiàn)low是一種可以連續(xù)發(fā)出多個值的類型,而不是只返回單個值的掛起函數(shù)。

For example ,you can use a Flow to receive live updates from database.
例如,您可以使用Flow從數(shù)據(jù)庫接收實時更新。

suspend function

suspend fun loadData():Data

Here,we have the suspend function loadData returns an object of type Data.
在這里,我們讓掛起函數(shù)loadData返回一個Data類型的對象。

uiScope.launch{
  val data = loadData()
  updateUi(data)
}

In hte UI layer,we can call that function from a Coroutine,for example ,created using a UI scope.
在UI層,我們可以從協(xié)程調(diào)用該函數(shù),例如,使用UI作用域創(chuàng)建的協(xié)程。
When loadData returns,we update the UI with its result.

當loadData返回時,我們用它的結果更新UI。
This is a one-shot call.
是一次性調(diào)用。
You call a suspend function, you get the result back.That is it.
你調(diào)用一個掛起函數(shù),就會得到結果。


Flow 函數(shù)

But a Flow is different.
Flow is built on top of Coroutines.
And since a Flow can emit multiple values sequentially,it is conceptually a stream of data whose values can be computed asynchronously.
但是,F(xiàn)low是不同的。Flow在協(xié)程上構建。而且由于Flow可以連續(xù)地產(chǎn)生多個值,因此它在概念上是一個數(shù)據(jù)流,其值可以異步計算。

fun dataStream():Flow<Data>

Here ,we have a function that returns a Flow of type Data,meaning that this Flow is able to emit data objects.
Notice that this function is no longer a suspend function.
這里是一個返回Flow類型的函數(shù),返回值Flow<Data>,這意味著該Flow能夠emit(發(fā)出)數(shù)據(jù)對象。
注意,這個函數(shù)不再是掛起函數(shù)。

Flow的collect是一個掛起函數(shù)
uiScope.launch{
  dataStream().collect{
    updateUi(data)
  }  
}

To trigger the Flow,we can call collect,which is suspend function and therefore needs to be called from a Coroutine.

要觸發(fā)Flow,我們可以調(diào)用collect,collect是掛起函數(shù),因此需要從協(xié)程調(diào)用。
In the lambda,we specify what to do when we receive an element from the Flow.
在lambda表達式中,我們指定從Flow接收元素時要做什么。

image.png

When a new element is emitted to the Flow, updateUi will be called with the new value.
當向Flow發(fā)出新元素時,updateUi函數(shù)會被調(diào)用,參數(shù)是新的element數(shù)據(jù)

New values will be processed until there are no more elements to emit to the Flow, or the UI goes away.
新的值將被處理,直到?jīng)]有更多的element可以發(fā)送到Flow,或者UI消失。

stream數(shù)據(jù)。這里涉及到三個實體。Producer、Consumer、intermediaries

What we've seen is a stream of data。
There are three entities involved here .
我們看到的是stream數(shù)據(jù)。這里涉及到三個實體。Producer、Consumer、intermediaries


image.png

The produrer produces data that is added to the stream.
Thanks to Coroutines ,Flows can also produce data asynchronously.

produrer生成添加到流中的數(shù)據(jù)。
多虧了協(xié)程,流還可以異步地生成數(shù)據(jù)。
The Consumer consumes the values from the stream.
In the previous example updateUi was the consumer.
Consumer消費流中的值。在前面的示例中,updateUi函數(shù)就是Consumer。

But there can also be intermediaries that can modify each value emitted into the stream or the stream itself.
但是也可以有intermediary,它們可以修改發(fā)送(emit)到流或流本身的每個值。

image.png

For example ,this intermediary changes the type of the emitted elements.
The consumer, in this case,will receive a different type.
例如,這個intermediary會更改發(fā)出的元素的類型。
在這種情況下,消費者(consumer)將收到不同的類型。

在android中描述生產(chǎn)者和消費者

In Android ,a data source or repository is typically a producer of UI data that has the view model, or view,as the consumer.

在Android中,數(shù)據(jù)源或repository通常是UI數(shù)據(jù)的生產(chǎn)者,viewModel或view作為消費者。
Other times, the view layer is a producer of user input events
and other layers of the hierarchy consume them.
Layers in between the producer and consumer usually act as intermediary that modify the stream of data to adjust it to the requirements of the following layer.
其他時候,視圖層是用戶輸入事件的生產(chǎn)者,視圖層次結構的其他層consume這些輸入事件。
生產(chǎn)者和消費者之間的層通常充當修改數(shù)據(jù)流的中間體(intermediary),以使其適應下一層的需求。

通過實例介紹Flow

Flow-retrofit簡圖

Now let's start with the producer and see how to create a Flow.
The builder function Flow creates a new Flow where you can manually emit new values into a stream of data using the Emit function.

現(xiàn)在讓我們從producer開始,看看如何創(chuàng)建一個Flow。
構建器函數(shù)Flow創(chuàng)建了一個新的Flow,您可以使用emit函數(shù)手動將新值發(fā)送到數(shù)據(jù)流中。
In the following example,we'll see an app that fetches the latest news periodically.
在下面的例子中,我們將看到一個定期獲取最新消息的應用程序。

class NewsRemoteDataSource(
 private val newsApi:NewsApi
) {
  val latestNews :Flow<List<ArticleHeadline>>{
    ...
  }
}

Here ,NewsRemoteDataSource has their property latestNews that returns a flow of Lists of ArticleHeadline.
As a single suspend function cannot return multiple consecutive values ,the data source needs to create and return a Flow in order to fulfill the requirement of having to notify all the latest news every so often.
在這里,NewsRemoteDataSource有其屬性latestNews,它返回一個列表的ArticleHeadline流(文章標題)。
由于單個掛起函數(shù)不能返回多個連續(xù)的值,數(shù)據(jù)源需要創(chuàng)建并返回一個Flow,以滿足必須經(jīng)常通知所有最新消息的需求。

In this case ,the data source acts as the producer.
在本例中,data source充當生產(chǎn)者。

interface NewsApi{
  suspend fun fetchLatestNews():List<ArticleHeadline>
}
//=======
class NewsRemoteDataSource(
 private val newsApi:NewsApi
) {
  val latestNews :Flow<List<ArticleHeadline>>{
    ...
  }
}

NewsRemoteDatasource takes NewsApi as a dependency,which is the class that ultimately makes the network request exposing a suspend funciton.

NewsRemoteDatasource將NewsApi作為依賴項,NewsApi類是最終使網(wǎng)絡請求暴露掛起函數(shù)。


class NewsRemoteDataSource(
 private val newsApi:NewsApi
) {
  val latestNews :Flow<List<ArticleHeadline>> = flow {
     //in a coroutine  -- can suspend
  }
}

So what's the implementation of the latestNews property?
We'll use the builder function Flow to create a new Flow.
那么latestNews屬性的實現(xiàn)是什么呢?
我們將使用構建器函數(shù)Flow來創(chuàng)建一個新的Flow。
As the block of code will be executed in the context of a Coroutine, it can call suspend funcitons.

由于代碼塊將在Coroutine的上下文中執(zhí)行,它可以調(diào)用suspend函數(shù)。

class NewsRemoteDataSource(
 private val newsApi:NewsApi
) {
  val latestNews :Flow<List<ArticleHeadline>> = flow {
     while(true){
       //todo 數(shù)據(jù)請求 和發(fā)送
     }
  }
}

To repeatedly make network requests,we create a while loop.
為了重復發(fā)出網(wǎng)絡請求,我們創(chuàng)建了一個while循環(huán)。

class NewsRemoteDataSource(
 private val newsApi:NewsApi
) {
  val latestNews :Flow<List<ArticleHeadline>> = flow {
     while(true){
       val latestNews = newsApi.fetchLatestNews()
       emit(latestNews) //發(fā)射發(fā)布
     }
  }
}

Inside it ,we call the Api to get the latest news.
And with its result,we call emit to add that object to the Flow.

在它內(nèi)部,我們調(diào)用Api來獲取最新的新聞。
然后調(diào)用emit將該latestNews對象添加到Flow中。
Flows are sequential.
And as the producer is in a Corountine,when calling a suspend function,the producer suspend until the suspend function returns.
In this case ,the Coroutine suspends until fetchLatestNews returns the response.
Only the emit is called .
流動是連續(xù)的。
由于生產(chǎn)者在Corountine中,當調(diào)用suspend函數(shù)時,生產(chǎn)者會一直掛起直到suspend函數(shù)返回。
在例子中,協(xié)程掛起直到fetchLatestNews返回響應。
只有emit被調(diào)用。

class NewsRemoteDataSource(
 private val newsApi:NewsApi,
 private val refeshInterValMs:Float = 5000f //默認時間間隔
) {
  val latestNews :Flow<List<ArticleHeadline>> = flow {
     while(true){
       val latestNews = newsApi.fetchLatestNews()
       emit(latestNews) //發(fā)射發(fā)布
       delay(refeshInterValMs)
     }
  }
}

To make requests on a fixed interval,we can call delay with a refresh interval passed as a parameter.
Delay is a suspend function that suspends the Coroutine for some time.
After that time,the Coroutine will resume,and another iteration in the while loop will happen.
要按固定的間隔發(fā)出請求,我們可以調(diào)用delay,并將刷新間隔作為參數(shù)傳遞。
Delay是一個掛起函數(shù),它將協(xié)程掛起一段時間。
在此之后,協(xié)程將繼續(xù)執(zhí)行,而while循環(huán)中的另一次迭代將會發(fā)生。

Flow中的CallbackFlow構造器

Something to keep in mind is that with a Flow Coroutine builder the producer cannot emit values form a different Coroutine context.
需要記住的是,在Flow協(xié)程構建器中,生成器不能發(fā)出形成不同協(xié)程上下文的值。
Therefore,don't call emit in your Coroutines or in with context blocks of code.
因此,不要在你的協(xié)程或代碼的上下文塊中調(diào)用emit。

You can use other Flow builders ,such as Callback Flow that we'll cover letter,for these cases.
對于這些情況,您可以使用其他流構建器,例如我們將介紹的CallbackFlow。
intermediary can use intermediate operators to modify the stream of data without consuming the values.
中介體可以使用中間操作符修改數(shù)據(jù)流,而不消費這些數(shù)據(jù)。
These operators are functions that ,when applied to a stream of data,set up a change of operations that aren't executed until the values are consumed in the future.
這些操作符是一些函數(shù),當應用于數(shù)據(jù)流時,它們會設置操作的更改,這些更改直到將來消費這些值時才會執(zhí)行。

class NewsRepository(
 private val newsRemoteDataSource: NewsRemoteDataSource,
 private val userData : UserData //比如用戶登陸返回的用戶信息
) {
  val favouriteLatestNews : Flow<List<ArticleHeadline>> = 
     newsRemoteDataSource.latestNews
    .map{news -> news.filter {userData.isFavouriteTopic(it) } }
    .onEach{ news -> saveInCache(news) }

}

Continuing our example,we have this NewsRepository.
In its constructor, it takes the NewsRemoteDataSource class that we've seen before ,as well as UserData to know more information about the logged in user.
繼續(xù)我們的例子,我們有這個NewsRepository。
在它的構造函數(shù)中,它接受我們以前見過的NewsRemoteDataSource類和UserData,UserData以了解關于登錄用戶的更多信息。
It exposes this favouriteLatestNews latest news preperty of type of Flow of a List of ArticleHeadlind.
它暴露了這個favoritelatestnews屬性,屬于Flow類型的ArticleHeadlind列表的最新新聞。
With this ,we want to expose just the news articles that are relevant to the user.
通過這種方式,我們希望只公開與用戶相關的新聞文章。

This function accesses the latestNews property from the newsRemoteDataSource.
And then it applies the map operator to modify the stream of data.
We use filter on the list that has been emitted to choose those articles whose topic the user is interested in .
The transformation happens on the Flow.
And the consumer will see the filters list instead of the original one.
Also,wo use onEach as a side effect to save the favorite user news in the cache.
此latestNews函數(shù)從newsRemoteDataSource訪問latestNews屬性。
然后它應用map操作符來修改數(shù)據(jù)流。
我們在已發(fā)出的列表上使用過濾器來選擇用戶感興趣的主題的文章。
轉(zhuǎn)換發(fā)生在流上。
消費者將看到過濾器列表而不是原來的列表。
此外,我們使用onEach作為一個副作用來保存最喜歡的用戶新聞在緩存中。

Intermediate operators can be applied one after the other,forming a chain of operations that are executed lazily when an item is emitted into the Flow.

中間操作符可以一個接一個地應用,形成操作鏈,在將項發(fā)送到Flow時惰性執(zhí)行這些操作。
Note that simply applying an intermediate operator to a stream does not start the Flow colleciotn.
To trigger the Flow and start listening for values,use a terminal operator.
\color{Pink} {請注意,簡單地對流應用中間操作符不會啟動Flow colleciotn}
要觸發(fā)流并開始監(jiān)聽值,請使用終端操作符。
With collect ,you get all values at the time they are emitted into the stream.
使用collect,您可以在所有值被釋放到流時獲得它們。

class LatestNewsViewModel(
   private val newsRepository: NewsRepository
)  :ViewModel(){
  init{
     viewModelScope.launch {
        newsRepository.favouriteLatestNews.collect{ favouriteNews ->
           //Update View with the latest favourite news 
       }
    }
  }
  
}

Now ,in our view model in this case ,the LatestNewsViewModel,we want to consume the Flow to get notified of the news and update the UI accordingly.
In there,we can call collect that triggers the Flow and starts using it for values.
The lambda will be executed on every new value received.
現(xiàn)在,在我們的視圖模型(在本例中是LatestNewsViewModel)中,我們希望消費Flow流以獲得新聞通知并相應地更新UI。
在那里,我們可以調(diào)用collect來觸發(fā)Flow并開始使用它來獲取值。
lambda表達式將在接收到的每個新值上執(zhí)行。
But, as we said,collect is a suspend function.
Therefore,it needs to be executed within a Coroutine that can create with the build-in ViewModelScope.
但是,就像我們說的,collect是一個掛起函數(shù)。
因此,它需要在一個可以用內(nèi)置的ViewModelScope創(chuàng)建的協(xié)程中執(zhí)行。
So what's really happening here ?
When the ViewModel is created ,we create a new Coroutine to collect the results from favouriteLatestNews.
那么到底發(fā)生了什么呢?
當ViewModel被創(chuàng)建時,我們創(chuàng)建一個新的Coroutine從favoritelatestnews收集結果。

This triggers the Flow in the data source layer,which will start fetching the latest news from the network.
這將觸發(fā)數(shù)據(jù)源層中的Flow,它將開始從網(wǎng)絡獲取最新的新聞。
All emissions are modified by the map operator in the repository layer to grab the user's favorite topics.
After that,the repository will save that info in the cache.
And the view model will get the latest filtered information.
存儲庫層中的map操作符修改所有排放,以獲取用戶最喜歡的主題。
之后,存儲庫將在緩存中保存該信息。
視圖模型將得到最新的過濾信息。

As the producer remains always active with the while loop,the stream of data will be closed when the ViewModel is cleared and ViewModelScope is canceled.
由于生成器在while循環(huán)中始終處于活動狀態(tài),當ViewModel被清除和ViewModelScope被取消時,數(shù)據(jù)流將被關閉。


Flow collection停止

There are two ways Flow collection can stop.
One way is when producer finishes emitting items.The Stream of data is closed.
And the Coroutine that called collect will resume executing.
Or alternatively,the Coroutine that collects is canceled ,as in our example.
This will also stop the underlying producer.

有兩種方法可以停止流收集。
一種方法是當生產(chǎn)者完成輸出項目時。數(shù)據(jù)流關閉。
調(diào)用collect的協(xié)程會繼續(xù)執(zhí)行。
或者,Coroutine的收集被取消,就像我們的例子。
這也將停止?jié)撛诘纳a(chǎn)者。

通常Flow是冷、懶加載

Flows are cold and lazy unless specified with other intermediate operators.
This means that producer code will be executed each time a terminal operator is called on the Flow.
流是冷的和惰性的,除非用其他中間操作符指定。
這意味著每次在Flow上調(diào)用終端操作符時都將執(zhí)行生成程序代碼。
In the example,multiple collectors of the Flow makes the data source to fetch the latest news multiple times on different fixed intervals.

在本例中,F(xiàn)low的多個收集器使數(shù)據(jù)源以不同的固定間隔多次獲取最新新聞。
See the shareIn operator to optimize and share the Flow when multiple consumers collect at the same time.
當多個消費者同時收集時,請參閱shareIn操作符來優(yōu)化和共享Flow。

第三方庫實現(xiàn)生產(chǎn)者

The implementation of the producer can come from a third-party libary.
生產(chǎn)者的實現(xiàn)可以來自第三方庫。
And as such ,it could throw unexpected exceptions.
To handle these exceptions ,use the catch intermediate operator.

因此,它可能會拋出意外異常。
要處理這些異常,請使用catch中間操作符。

class LatestNewsViewModel(
   private val newsRepository: NewsRepository
)  :ViewModel(){
  init{
     viewModelScope.launch {
        newsRepository.favouriteLatestNews
           .catch{ exception -> notifyError(exception) }
           .collect{ favouriteNews ->
           //Update View with the latest favourite news
       }
    }
  }
  
}

Again ,in the ViewModel layer,to catch unexpected exceptions,
we can use the catch operator to handle them and show the right message to the user.
As catch is an intermediate operator,it needs to be called before collect.

同樣,在ViewModel層中,為了捕獲意外異常,我們可以使用catch操作符來處理它們,并向用戶顯示正確的消息。

\color{Pink} {由于catch是一個中間操作符,因此需要在collect之前調(diào)用它。}

class NewsRepository(
 private val newsRemoteDataSource: NewsRemoteDataSource,
 private val userData : UserData //比如用戶登陸返回的用戶信息
) {
  val favouriteLatestNews : Flow<List<ArticleHeadline>> = 
     newsRemoteDataSource.latestNews
    .map{news -> news.filter {userData.isFavouriteTopic(it) } }
    .onEach{ news -> saveInCache(news) }
    //If an error happens,emit the last cached values 如果網(wǎng)絡范圍錯誤,那么使用最新的cache數(shù)據(jù)
    .catch{ exception-> emit(lastCachedNews() }

}

But catch can also emits to the Flow.
If we wanted to handle those unexpected exceptions in a repository layer, we can use the catch operator and emit to the Flow using the emit function with the latest cached news.
但catch也可以發(fā)出流。
如果我們想在存儲庫層處理那些意外的異常,我們可以使用catch操作符,并使用帶有最新緩存新聞的emit函數(shù)向Flow發(fā)出消息。

If we talk about Android specifically Flow is integrated in many JetPack libraries.
And it's popular among Android third-party libraries as well.
如果我們專門討論Android, Flow集成在許多JetPack庫中。
它在Android第三方庫中也很受歡迎。

Flow &Room配合使用

Flow is a great fit for data updates.
Flow非常適合數(shù)據(jù)更新。

您可以查看官方文章使用 Flow with Room 接收有關數(shù)據(jù)庫更改的通知

@Dao
abstract class ExampleDao {
    @Query("SELECT * FROM Example ")
    abstract fun getExamples() :Flow<List<Example>>
}

For example ,you can use Flow with Room to be notified of changes in your database.
As shown in the code snippet, in the Dao,return a Flow type to get live updates.
Every time there is a change in the Example table,a new List is emitted with the items in the database.
例如,您可以使用Flow和Room來通知數(shù)據(jù)庫中的更改。
如代碼片段所示,在Dao中,返回一個Flow類型以獲得實時更新。
每當在Example表中發(fā)生更改時,就會觸發(fā)一個新的List,其中包含數(shù)據(jù)庫中的項目。

callbackFlow構造器

At this point, you pretty much know everything you need about Flows.
在這一點上,您幾乎已經(jīng)了解了關于flow所需的所有內(nèi)容。
However,there is another Flow builder I want to talk about,as it is used quite often.
And that is callbackFlow that lets you convert callback-based APIs into flows.
然而,我想談談另一種流構建器,因為它被使用得非常頻繁。
這就是callbackFlow,它允許你將基于回調(diào)的api轉(zhuǎn)換為流。
As an example,the Firebase Firestore Android APIs use callbacks.
Let's see how to convert those callbacks to flow and listen for Firestore database updates.
例如,F(xiàn)irebase Firestore Android api使用回調(diào)。
讓我們看看如何將這些回調(diào)轉(zhuǎn)換為流并偵聽Firestore數(shù)據(jù)庫更新。

class FirestoreUserEventsDataSource(
    private val firestore :FirebaseFirestore
){
    //Method to get user events from the Firestore database
    fun getUserEvents() :Flow<UserEvents> { ... }
}

Here,we have a FirestoreUserEventsDataSource,whose getUserEvents method returns a Flow of UserEvents.
As you can see,we take an instance of FirebaseFirestore as a dependency.

在這里,我們有一個FirestoreUserEventsDataSource,它的getUserEvents方法返回一個UserEvents流。
如您所見,我們將FirebaseFirestore的一個實例作為依賴項。

class FirestoreUserEventsDataSource(
    private val firestore :FirebaseFirestore
){
    //Method to get user events from the Firestore database
    fun getUserEvents() :Flow<UserEvents> = callbackFlow {
        ...
    }
}

To create the flow, we use the callbackFlow API.
As with the Flow builder API, here ,we are in the context of a Coroutine.
But ,unlike the Flow builder,channelFlow allows values to be emitted from a different Coroutine context or outside a Coroutine with the offer method,as we'll see.

要創(chuàng)建流,我們使用callbackFlow API。
與Flow構建器API一樣,在這里,我們處于協(xié)程的上下文中。
但是,與Flow構建器不同的是,channelFlow允許從不同的協(xié)程上下文中或通過offer方法在協(xié)程外部發(fā)出值,我們將看到這一點。

image.png
class FirestoreUserEventsDataSource(    ...){
    var eventsCollection :CollectionReference ?=null
    try{
        eventsCollection = FirebaseFirestore.getInstance()
             .collection("collection")
             .document("app")
    }catch(e:Throwable){
    }
   
}

The first thing to do is initialize in Firebase and getting the eventsCollection from it.
Therefore,we write something like this code.
要做的第一件事是在Firebase中初始化并從中獲取eventsCollection。
因此,我們編寫了如下代碼。

class FirestoreUserEventsDataSource(    ...){
    var eventsCollection :CollectionReference ?=null
    try{
        eventsCollection = FirebaseFirestore.getInstance()
             .collection("collection")
             .document("app")
    }catch(e:Throwable){
        close(e) //關閉Flow
    }
   
}

However,this could potentially throw an an exception if it fails getting the eventsCollection.
If that happens,or Firebase cannot be initialized,we need to closed the Flow.
但是,如果獲取eventsCollection失敗,可能會拋出異常。
如果發(fā)生這種情況,或者Firebase無法初始化,則需要關閉Flow。

image.png

If Firebase can be initialized and we're able to get the eventsCollection,we need to add a callback using addSnapshotListener.
The callback lambda will be executed every time there is a change to eventsCollection.

image.png

In there ,we check if the snapshot of the collection that we receive is new or not .
And if not ,we call offer to emit an item to the flow.
Offer is not a suspend function. That's why we can call it from inside a callback.
在那里,我們檢查接收到的集合的快照是否為新的。
如果沒有,則調(diào)用offer將一個項發(fā)送到流。
Offer 不是掛起函數(shù)。這就是為什么我們可以在回調(diào)中調(diào)用它。

image.png

So far,we initialized Firestore and added the subscription.
What's next?
Now we want to keep the communication open with the consumer of the flow so that it can receive all events sent by Firestore.
到目前為止,我們初始化了Firestore并添加了訂閱。
接下來是什么?
現(xiàn)在,我們希望保持與流的使用者的通信打開,以便它能夠接收Firestore發(fā)送的所有事件。


image.png

For that ,we use the awaitClose method,which will wait until the flow is closed or canceled.
When that happens ,the callback inside awaitClose gets called.
In our case,we remove the subscription from Firebase.

將基于回調(diào)的 API 轉(zhuǎn)換為數(shù)據(jù)流的完整代碼

class FirestoreUserEventsDataSource(
    private val firestore: FirebaseFirestore
) {
    // Method to get user events from the Firestore database
    fun getUserEvents(): Flow<UserEvents> = callbackFlow {

        // Reference to use in Firestore
        var eventsCollection: CollectionReference? = null
        try {
            eventsCollection = FirebaseFirestore.getInstance()
                .collection("collection")
                .document("app")
        } catch (e: Throwable) {
            // If Firebase cannot be initialized, close the stream of data
            // flow consumers will stop collecting and the coroutine will resume
            close(e)
        }

        // Registers callback to firestore, which will be called on new events
        val subscription = eventsCollection?.addSnapshotListener { snapshot, _ ->
            if (snapshot == null) { return@addSnapshotListener }
            // Sends events to the flow! Consumers will get the new events
            try {
                offer(snapshot.getEvents())
            } catch (e: Throwable) {
                // Event couldn't be sent to the flow
            }
        }

        // The callback inside awaitClose will be executed when the flow is
        // either closed or cancelled.
        // In this case, remove the callback from Firestore
        awaitClose { subscription?.remove() }
    }
}

臨時代碼-suspend函數(shù)-這部分代碼僅僅是筆記

    data class User(val name: String, val age: Int)

    /**
     * 掛起函數(shù)
     */
    suspend fun loadUser():User {
        sleep(300)//模擬網(wǎng)絡請求等
        return User("小李哥",17)
    }

   override fun onCreate(savedInstanceState: Bundle?) {
       ...
        GlobalScope.launch {
            var myUser =loadUser()
            //todo 更新myUser到UI
        }
       ...
   }


suspend 與flow函數(shù) -例子1

    /**
     * 流flow函數(shù)定義
     */
    fun listFlow() = flow<List<String>> {
        emit(getMylist())
    }

    /**
     * 為流函數(shù)提供數(shù)據(jù)
     */
    suspend fun getMylist(): List<String> {
        delay(6666L)
        return mutableListOf<String>("1", "2", "3")
    }

suspend與flow -例子2

    //flow 例子3
    interface UsersApi{
        suspend fun fetchtUsers():List<User>
    }

    class NewsRemoteDataSource(
        private val usersApi:UsersApi
    ) {
        val latestNews :Flow<List<User>> = flow {
            while(true){
                //todo 數(shù)據(jù)請求 和發(fā)送
                val latestNews = usersApi.fetchtUsers()
                emit(latestNews) //發(fā)射發(fā)布
                delay(500)
            }
        }
    }

結束語

Yay,you made it to the end.
Hope you learned a lot in this video.
Namely,what problems Flow solve,how you can create Flows and observe them,and how powerful they can be with the intermediate operators.
You can learn more about Flow in our developer.android.com documentation.
Thanks for watching. And go write better Android apps with Kotlin.

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內(nèi)容