😊

初めてのStateFlowと注意点

2020/09/28に公開

Noteの記事を加筆修正したものです。

StateFlowとは?

アプリケーションで更新可能な状態を表現するためにFlowを便利に使えるようにする必要があります。
今回の変更では、StateFlow -- 状態を表す更新可能な値であり、FlowであるStateFlowが導入されました。様々なニーズに柔軟に対応できるように設計されています。
StateFlow<T>インターフェイスは、現在の値へのアクセスを与える読み取り専用のビューであり、値への更新を収集するためのFlow<T>を実装しています。
MutabaleStateFlow<T>インターフェイスは、値の変更操作を追加します。
MutableStateFlow(x)のコンストラクタ関数が提供されています。
これは、与えられた初期値を持つMutableStateFlowの実装を返します。
値への非反応的な高速アクセスが必要な場合はStateFlow<T>として、値への更新の反応的な表示のみが必要な場合はFlow<T>として外部に公開することができます。
コアとなるステートフローAPIは以下のようにまとめることができます。

www.DeepL.com/Translator(無料版)で翻訳しました。

値の取得ができたりMutableStateFlowなら値のセットもできるみたいですね。
完全に理解できたところで触っていきます。

ViewModel

class SubFragmentViewModel(val id: Int, val repository: Repository) : ViewModel() {
    val _message = MutableStateFlow<String?>(null)
    val message: StateFlow<String?> get() = _message
    val flowMessage: Flow<String?> get() = _message

    fun setMessage(message: String?) {
        _message.value = message
    }

    fun loadMessage() {
        Log.d("AAAAAAAAAAAAAA", message.value)
    }

    fun loadMessage2() {
        viewModelScope.launch {
            message.collect {
                Log.d("BBBBBBBBBBBBBBB", it)
            }
        }
    }

    fun loadMessage3() {
        viewModelScope.launch {
            flowMessage.collect {
                Log.d("CCCCCCCCCCCCCCCC", it)
            }
        }
    }
}

MutableStateFlowの_messageとそれを参照してStateFlowにキャストしているmessage、FlowにキャストしているflowMessageを用意しました。

setMessage()を呼ぶ事でMutableStateFlowの_messageに文字列をセットします。
セットされた値を読み込む物はloadMessage()を用意し、
StateFlowのmessageからgetValueする、collectして変更があれば読み込むものと
FlowのflowMessageからcollectして値を読み込む物を用意しました。

Fragment

class SubFragment : Fragment() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        viewModel.setMessage("TEST")
        viewModel.setMessage("TEST2")
        viewModel.setMessage("TEST3")
        viewModel.loadMessage()
        viewModel.loadMessage2()
        viewModel.loadMessage3()
        viewModel.setMessage("TEST4")
        viewModel.setMessage("TEST5")
    }
}

FragmentではViewModelで用意した文字列をセットするsetMessageを3回読んだ後に
値を読み込むloadMessageを呼び、最後に再度文字列をセットした時の挙動を確認する事にしました。

動作結果

2020-05-20 07:12:13.503 29370-29370/com.example.masterofkoin D/AAAAAAAAAAAAAA: TEST3
2020-05-20 07:12:13.559 29370-29370/com.example.masterofkoin D/BBBBBBBBBBBBBBB: TEST5
2020-05-20 07:12:13.562 29370-29370/com.example.masterofkoin D/CCCCCCCCCC

StateFlowのgetValueは呼び出した時点の値しか取れない事は予想通りでしたが、
Collectした時に最新の値しか取れないのは予想外でした。

再検証(delayを入れて値をセット)

fun pushMessage() {
    viewModelScope.launch {
        setMessage("TEST")
        setMessage("TEST2")
        setMessage("TEST3")
        delay(500)
        setMessage("TEST4")
        setMessage("TEST5")
    }
}

メッセージのセット中にdelayを入れてみました。

2020-05-20 08:08:07.574 6563-6563/com.example.masterofkoin D/BBBBBBBBBBBBBBB: TEST3
2020-05-20 08:08:07.575 6563-6563/com.example.masterofkoin D/CCCCCCCCCCCCCCCC: TEST3
2020-05-20 08:08:08.075 6563-6563/com.example.masterofkoin D/BBBBBBBBBBBBBBB: TEST5
2020-05-20 08:08:08.076 6563-6563/com.example.masterofkoin D/CCCCCCCCCCCCCCCC: TEST5

無事にTEST3/TEST5の2回Collectする事ができました。

まとめ

毎回必ず変更を検知したい場合には注意が必要。

Discussion