[Kotlin]Flow/StateFlow/SharedFlowの解説

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)