【Android × Kotlin】StateFlow で値を監視する
今回は、StateFlow で値を監視する方法を解説します。
なお、ここに掲載しているソースコードは以下の環境で動作確認しています。
- Android Studio Electric Eel | 2022.1.1
- JDK 11.0.15
- Android Gradle Plugin 7.4.0
- Kotlin 1.8.0
- Gradle 7.6.0
- org.jetbrains.kotlinx:kotlinx-coroutines-android 1.6.4
- androidx.lifecycle 2.5.1
StateFlow とは?
StateFlow は状態保持に特化した Flow です。最新の値のみ保持する特徴を持っているため、UI の状態保持に最適で、この Flow で UI の状態を保持することで、画面の表示を常に最新に維持することができます。
また、Android Jetpack には Activity や Fragment などのライフサイクルを考慮して suspend 関数を呼ぶ関数が用意されています。よって、Activity や Fragment がフォアグラウンドに表示されているときだけ StateFlow から値を収集することが可能であり、これにより、メモリリークやクラッシュを回避できます。
StateFlow を使う
実際に StateFlow を試します。
ViewModel の解説のときと同様に、Button をクリックした回数を ViewModel に保持し、その回数を Activity の TextView に表示してみます。
このとき、Button のクリック回数を素の Int 型で保持するのではなくて、StateFlow で保持します。
ライブラリのインポート
Android で StateFlow を使う場合、アプリの build.gradle ファイルに次の依存関係を追加します。
dependencies { // Kotlin Coroutines implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4" // AndroidX Lifecycle implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1" implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1" }
まず、Android で StateFlow を使う場合には org.jetbrains.kotlinx:kotlinx-coroutines-android
をインポートします。また、以降のサンプルで StateFlow を ViewModel に保持するためにlifecycle-viewmodel-ktx
をインポートし、Activity で安全に StateFlow の値を収集するために lifecycle-runtime-ktx
をインポートします。
ViewModel で値を保持する
まず、ViewModel を以下のように実装します。
class MainViewModel : ViewModel() { /** Button のクリック回数 */ private val _count = MutableStateFlow(0) val count = _count.asStateFlow() /** クリック回数をカウントアップする。 */ fun countUp() { _count.value = count.value + 1 } }
最初に、StateFlow で値を保持する箇所を見ていきます。
/** Button のクリック回数 */ private val _count = MutableStateFlow(0) val count = _count.asStateFlow()
StateFlow には大きく分けて MutableStateFlow と StateFlow の 2 つのクラスが存在します。この 2 つは以下のような違いがあります。
クラス | 特徴 |
---|---|
MutableStateFlow | 値を変更できる。 |
StateFlow | 値を変更できない。 |
StateFlow を扱う場合、値の変更は ViewModel 内部に留めて、値の変更通知を受け取る Activity や Fragment などではその値を変更できないようにします。
よって、値そのものは private が付与された MutableStateFlow で保持して、それを StateFlow に変換したものを ViewModel の外部に公開します。
次に、クリック回数をカウントアップするメソッドを見ていきます。
/** クリック回数をカウントアップする。 */ fun countUp() { _count.value = count.value + 1 }
MutableStateFlow と StateFlow には value プロパティというものがあり、このプロパティを使って値の取得や設定ができます。
Activity で値の変更通知を受け取る
そして、Activity を以下のように実装します。
class MainActivity : AppCompatActivity(R.layout.main_activity) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val viewModel = ViewModelProvider(this)[MainViewModel::class.java] // ボタンがクリックされるたびに、クリック回数をカウントアップする。 findViewById<Button>(R.id.button) .setOnClickListener { viewModel.countUp() } // クリック回数が変更されたら、TextView を更新する。 lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.count.collect { findViewById<TextView>(R.id.text).text = it.toString() } } } } }
まず、Button がクリックされたら ViewModel のカウントアップメソッドを呼びます。
// ボタンがクリックされるたびに、クリック回数をカウントアップする。
findViewById<Button>(R.id.button)
.setOnClickListener {
viewModel.countUp()
}
そして、ViewModel のクリック回数の変更通知を監視して、クリック回数を TextView に設定します。
// クリック回数が変更されたら、TextView を更新する。
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.count.collect {
findViewById<TextView>(R.id.text).text = it.toString()
}
}
}
クリック回数を保持する StateFlow は Flow なので collect メソッドで収集します。
しかし、直接収集すると、Activity が破棄された後やバックグラウンドにいるときも収集してしまいます。
そこで、Activity の lifecycleScope と repeatOnLifecycle メソッドを使います。
Activity の lifecycleScope は Activity の CoroutineScope であり、Activity が破棄されると実行中の Coroutine がキャンセルされます。
そして、repeatOnLifecycle メソッドはライフサイクルが指定された状態になったときに渡されたブロックを呼びます。
今回のサンプルの場合、ライフサイクルが Start から Stop の間にブロックを呼びます。
この repeatOnLifecycle のブロックの中でクリック回数の StateFlow を収集し、 収集したクリック回数を TextView に設定します。
StateFlow VS LiveData
StateFlow と似た機能を持つものとして、LiveData があります。
StateFlow は LiveData と比較すると、値を null 安全で保持することができるため、StateFlow の方を積極的に使うとよいでしょう。
参考
関連記事
ViewModel の使用方法については、以下で解説しています。
LiveData で値を監視する方法については、以下で解説しています。