Coroutines 1.3.6 から StateFlow
が導入されました。これは使い方等がLiveDataと似ているため、どういった違いがあるのか調べてみました。
これを書いてるときはまだ Exprimental なので、今後変更があるかもしれません。今回は1.3.7を使っています。
使い方
まずは StateFlow
の簡単な使い方です。
LiveDataと同じように定義してあげます。 MutableStateFlow
をprivateにして、 StateFlow
として外からは変更不可で公開してあげるのが丁寧ですね。
この例では数値をインクリメントしていくような処理になっています。
この StateFlow
の変更通知を受け取る方法です。
変更通知を受け取るには collect
を使います。また、 collect
は suspend
関数になってるので、 lifecycleScope
を使っています。
見ての通り LiveData
と使い方は非常に似ています。これらの違いについて分かったことをまとめていきたいと思います。
初期値
MutableLiveData
は元々、初期値をコンストラクタ等で設定することはできませんでした。最近になってコンストラクタで初期値を渡すことができるようになりました。val _liveData = MutableLiveData()
のようにパラメータなしコンストラクタが可能です。
逆に MutableStateFlow
はコンストラクタに初期値が必要です。もしnullにしたければ nullable な型パラメータで宣言する必要があります。
最初の変更通知
初期値の箇所と関連しますが、 MutableLiveData
の場合はコンストラクタに何も渡さなければ、 observe
したタイミングでは何も通知されません。
MutableStateFlow
の場合は必ずコンストラクタに初期値にパラメータが必要になります。そのため collect
したタイミングで初期値が必ず通知されることになります。これはコンストラクタにnullを設定した場合でも通知されます。
NonNull / Nullable
LiveData
はJavaで実装されてるため型パラメータをNonNullな型で指定しても、nullを設定することが可能になっています。
StateFlow
のほうは実装もKotlinのため型パラメータがNonNullであれば、nullを設定することができず、安全に使用することが可能です。
補足: lifecycle-livedata-core-ktx:2.3.0-alpha03
からNonNullな型パラメータの場合はnullを設定するとlintチェックでエラーになるようになっています。
メインスレッド / バックグラウンドスレッド
MutableLiveData
の場合はバックグラウンドスレッドから値を設定するときは value = xxx
ではなく、 postValue(xxx)
を使う必要がありました。
また、 observe
するときはメインスレッドである必要があります。
MutableStateFlow
の場合は、メインスレッド・バックグラウンドスレッド関係なく value = xxx
を使います。
StateFlow
の場合は CoroutineContext によって値を受け取るときのスレッドを制御することができます。
細かい説明は省略しますが、 lifecycleScope
はデフォルトではメインスレッドで動くようになっています。これは制御可能で、次のようにすることで collect
の処理を IO Workerで動かすことが可能になります。そのため、UI変更の処理をしてるともちろんクラッシュします。
StateFlow
は柔軟に制御することが可能となっています。
Lifecycle
LiveData
は LifecycleOwner が STARTED
以降の場合に通知され、もしアプリがバックグラウンドにいる場合は通知されません。
(LifecycleOwner を自作したりすることでコントロール可能)
StateFlow
は CoroutineContext が有効な間は通知が受け取れるようになっています。自分で Job
などで CoroutineContext を作って細かくコントロールすることも可能です。 cancel()
を呼び出してそれ以降、通知を受け取れなくすることも可能です。
lifecycleScope
を使う場合ですが、 lifecycleScope.launch
を使うと CREATED
以降の場合に通知されるので、アプリがバックグラウンドにいっても通知を受け取ります。もし、 LiveData
と同じライフサイクルにしたい場合は、 lifecycleScope.launchWhenStarted
を使います。
Operator
LiveData
と違って StateFlow
はベースが Flow
なので、強力なオペレーターが用意されています。
良い例が思い浮かばなかったですが、これ上の例以外にもいくつかオペレーターが用意されています。
複数Observer
LiveData
は複数 observe
した場合もそれぞれ通知が届きます。
StateFlow
は特殊です。同じ CoroutineContext 内で何回 collect
しても通知が届くのは最初に collect
した箇所のみです。
また、別の CoroutineContext で collect
した場合は、それぞれに通知が届きます。
DataBinding
説明する必要もなさそうですが一応。 StateFlow
は DataBinding には対応していません。
まとめ
LiveData
と StateFlow
、似たような構文ですが、色々違う点があります。
LiveData
を単純に置き換えるには違う点がいくつかあるので、注意が必要だと思います。また、逆に無理に置き換える必要は特にない印象です。
StateFlow
はベースが Flow
なので、 asLiveData()
で LiveData
に変換可能ではあります。(これはこれで気をつけることがありますが)
特徴をしっかり理解して、使っていきたいところです。
おまけ
次のような拡張関数を作っておくと、 LiveData
と同じような使い方ができます。