Flow
まず普通のFlowから。
val flow = flow {
Log.d("###test", "emit")
emit(1)
emit(2)
emit(3)
}
flow.onEach {
Log.d("###test", "first onEach $it")
}.launchIn(viewModelScope)
runBlocking { delay(1000) } //
flow.onEach {
Log.d("###test", "secound onEach $it")
}.launchIn(viewModelScope)
}
こうするとログはこうなる。
###test: emit
###test: first onEach 1
###test: first onEach 2
###test: first onEach 3
###test: emit
###test: secound onEach 1
###test: secound onEach 2
###test: secound onEach 3
ここからわかることは、flowは受け取り側が購読を始めると、その購読をした回数だけ生産者側が送信を開始する。
val flow = (省略)
flow emit(1)
ちなみにcold streamなのでflow変数に対して直接emitすることはできない。
SharedFlow
SharedFlowはhot streamなのでflow変数に対して直接emitできる。だだし、emitはsuspend関数なのでコルーチンを起動して呼ぶ必要がある。
val mutableSharedFlow = MutableSharedFlow<Int>()
mutableSharedFlow.onEach {
Log.d("###test", "first onEach $it")
}.launchIn(viewModelScope)
mutableSharedFlow.onEach {
Log.d("###test", "second onEach $it")
}.launchIn(viewModelScope)
runBlocking {
Log.d("###test", "emit")
mutableSharedFlow.emit(1)
mutableSharedFlow.emit(2)
}
ログはこうなる。
###test: emit
###test: first onEach 1
###test: second onEach 1
###test: first onEach 2
###test: second onEach 2
flowと異なってemitは1セットしか読んでいないが、購読者は全員値を受け取っている。
さてここで問題。SharedFlowの上記例では購読を開始した後にemitを読んでいるが、emitをした後に購読するとどうなるでしょう?
val mutableSharedFlow = MutableSharedFlow<Int>()
runBlocking {
mutableSharedFlow.emit(1)
mutableSharedFlow.emit(2)
Log.d("###test", "emit")
}
mutableSharedFlow.onEach {
Log.d("###test", "first onEach $it")
}.launchIn(viewModelScope)
mutableSharedFlow.onEach {
Log.d("###test", "second onEach $it")
}.launchIn(viewModelScope)
答えはこれ。
2021-06-28 06:15:12.249 4204-4204/com.example.myapplication D/###test: emit
何もデータを受け取らない。購読した後にemitしないとデータを受け取れない。ちなみに重複データは流す仕様である。
StateFlow
ソースとログをみれば挙動がわかると思う。
val mutableStateFlow = MutableStateFlow(0)
mutableStateFlow.onEach {
Log.d("###test", "first onEach $it")
}.launchIn(viewModelScope)
runBlocking { delay(1000) }
Log.d("###test", "emit value:1")
mutableStateFlow.value = 1
mutableStateFlow.onEach {
Log.d("###test", "second onEach $it")
}.launchIn(viewModelScope)
runBlocking { delay(1000) }
Log.d("###test", "emit value:2")
mutableStateFlow.value = 2
}
ログはこんな感じ。
###test: first onEach 0
###test: emit value:1
###test: first onEach 1
###test: second onEach 1
###test: emit value:2
###test: first onEach 2
###test: second onEach 2
特徴は初期値が必ず必要で、購読を開始するとSharedFlowが保持している最新の値を取得して、そのあとにデータが更新されると購読している全員が値を受信する挙動となる。ちなみに重複データは流さない仕様である。また、onEachしなくても直接valueを覗くこともできる。
Log.d("###test", "value is " + mutableStateFlow.value)
asLiveDataについて
FlowはliveDataに変化する便利なAPIが用意されていて、使用するためにはgradleに以下を記載する必要がある。
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
asLiveData()を呼ぶと変換できる。
val mutableStateFlow = MutableStateFlow(0)
mutableStateFlow.asLiveData()
asLiveDataの引数にCoroutineContextを入れると例えばViewModelのライフサイクルと紐づけたりできる。
mutableStateFlow.asLiveData(viewModelScope.coroutineContext)