ガラパゴスに入社して2ヶ月弱、AIR Design for Apps事業部 Androidチームの務台です。
私は現在JetpackComposeとMVVMアーキテクチャを用いたプロジェクトに携わっていますが、ふと疑問が沸きました。
ViewModelでの状態管理って、どのクラス使うのが良いんだ?
というわけで調べてみました。
結論
State
、LiveData
、Flow
、RxJava2
のいずれかを使用することができるが、
ViewModelでの状態管理には基本的にはFlowを用いると良い。
ただし、Composable内でクローズな状態管理をする、XMLのレイアウトからJetpackComposeに移行する等、
他のものを使用した方が良いケースもある。
調べたこと
以下、状態管理クラスについての調査結果になります。
使用可能なクラス
ViewのComposable関数からViewModelの状態を監視して値を更新していくためには、ViewでState<T>
として取得できるクラスである必要があります。
これはAndroid Developersガイドによると
- State
- LiveDate
- Flow
- RxJava2
のいずれかが使用可能です。
この中でState
とLiveData
はAndroidに依存したライブラリであり、ライフサイクルに関する問題はライブラリが良きに計らってくれるので、実装者は意識する必要がなくなります。
Flow
とRxJava2
はKotlin(Java)のライブラリで、本来は非同期処理を行うためのものですが、JetpackComposeの拡張関数により状態管理にも使用することができるようになりました。
これによって、ViewModelやModelでの状態管理を一貫してFlow
またはRxJava2
を使用することができます。
各クラスの調査
状態管理に使用可能な4種類のクラスについてそれぞれ使い方や特徴等を調べてみました。
State
状態管理に使用することができるものの中で最もシンプルなクラスで、mutableStateOf()
でインスタンスを作成することができます。
State
を利用した状態管理の例:
val messageState = mutableStateOf<String>("")
fun onChangeMessage(newValue: String) {
messageState.value = newValue
}
val receivedMessageState by viewModel.messageState
TextField(
value = receivedMessageState,
onValueChange = viewModel::onChangeMessage,
)
onChangedMessage
でMutableState.value
が変更されると、その値を使用しているComposableへ通知が飛び、値が更新されます。
かなりシンプルに使うことができる分、非同期処理や複数の状態を関連させる等の複雑な動きが絡んでくると管理が難しくなりそうです。
また、View側でremember
と組み合わせて、Composableの外部に公開しない、ステートフルな状態を管理する用途に使用することもできます。
remember
と併用した例:
val messageState = remember { mutableStateOf<String>("") }
TextField(
value = viewModel.messageState.value,
onValueChange = {
messageState.value = it
},
)
LiveData
監視可能なデータホルダクラスで、ライフサイクルに応じた監視が可能であるため、余計な心配事を減らすことができるクラスです。
ViewModel側にLiveData
を置き、View側でobserveAsState()
を呼ぶことで、ViewModelでの変更を監視してComposableへ通知を送ることができます。
LiveData
を利用した状態管理の例:
private val _messageState = MutableLiveData<String>("")
val messageState: LiveData<String> = _messageState
fun onChangeMessage(newValue: String) {
_messageState.value = newValue
}
val receivedMessageState by viewModel.messageState.observeAsState("")
TextField(
value = receivedMessageState,
onValueChange = viewModel::onChangeMessage,
)
比較的簡単に使用することができるため、とりあえず動かしてみたいという場合や、
DataBindingを用いた既存プロジェクトをJetpackComposeへ移行する場合
に使用することができるかと思います。
Flow
Flowを用いた状態管理にはStateFlow
を用いることができます。
coroutineの一種であるFlowを状態管理の用途に絞ったものがStateFlow
で、
suspend関数とは異なり複数の値を順次出力することができます。
ViewModel側にStateFlow
を置き、View側でcollectAsState()
を呼ぶことで、ViewModel側での変更をComposableへ通知することができます。
StateFlow
を用いた状態管理の例:
private val _messageState = MutableStateFlow<String>("")
val messageState: StateFlow<String> = _messageState
fun onChangeMessage(newValue: String) {
viewModelScope.launch {
_messageState.value = newValue
}
}
val receivedMessageState by viewModel.messageState.collectAsState()
TextField(
value = receivedMessageState,
onValueChange = viewModel::onChangeMessage,
)
今回調査した中では最も後発のクラスで、LiveData
とFlow
を組み合わせたような使い方が可能です。
また、以下のようにcombine
を用いて関連する状態をまとめて監視することもできます。
combine
を用いて二つのメッセージの合計文字数を管理する例:
private val _firstMessageState = MutableStateFlow<String>("")
val firstMessageState: StateFlow<String> = _firstMessageState
private val _secondMessageState = MutableStateFlow<String>("")
val secondMessageState: StateFlow<String> = _secondMessageState
val messageCountState: StateFlow<Int> = combine(
_firstMessageState,
_secondMessageState,
) { firstMessage, secondMessage ->
firstMessage.count() + secondMessage.count()
}.stateIn(viewModelScope, SharingStarted.Eagerly, 0)
fun onChangeFirstMessage(newValue: String) {
viewModelScope.launch {
_firstMessageState.value = newValue
}
}
fun onChangeSecondMessage(newValue: String) {
viewModelScope.launch {
_secondMessageState.value = newValue
}
}
val receivedFirstMessageState by viewModel.firstMessageState.collectAsState()
val receivedSecondMessageState by viewModel.secondMessageState.collectAsState()
val receivedCountState by viewModel.messageCountState.collectAsState()
TextField(
value = receivedFirstMessageState,
onValueChange = viewModel::onChangeFirstMessage,
)
TextField(
value = receivedSecondMessageState,
onValueChange = viewModel::onChangeSecondMessage,
)
Text(text = receivedCountState.toString())
非同期によるデータの取得からFlow
を使うことで、一貫してFlow
のみで状態管理を行うことができるため、
ViewModelでの状態管理にはこれを利用するのが良いのではないかと思います。
RxJava2
Reactive ExtensoionsをJavaで実装したライブラリで、これに拡張機能を追加したRxKotlinというライブラリも存在します。
Reactive Extensionsとは、Observable<T>
とObserver<T>
インターフェースを用いて非同期通信を行うことでデータを受け渡そう、という考えみたいですが、調べても理解が難しかったためあまり触れないでおきますごめんなさいゆるして。
RxJava2では様々なユースケースで非同期処理を行うことができる機能が多く備わっているようです。
RxJava2
を用いた状態管理の例:
private val _messageState: PublishSubject<String> = PublishSubject.create()
val messageState = _messageState
fun onChangeMessage(newValue: String) {
viewModelScope.launch {
_messageState.onNext(newValue)
}
}
val receivedMessageState by viewModel.messageState.subscribeAsState("")
TextField(
value = receivedMessageState,
onValueChange = viewModel::onChangeMessage,
)
上記のように状態管理に使用することはできますが、多機能故に学習コストが高くなってしまい、現在では似たようなことを簡単にできるFlow
があることから、新しく採用する場面はほぼ無いのではないかと思います。
まとめ
JetpackComposeでの状態管理には基本的にFlow
を使うのが良いのではないかと思いますが、
他のクラスも以下の表のように、状況によっては使用することがありそうです。
クラス名 |
用途 |
State |
シンプルなViewModelでの状態管理、remember と併用してクローズな状態管理 |
LiveData |
既存プロジェクトのJetpackComposeへの移行 |
Flow |
ViewModelでの状態管理、データソースの非同期での取得 |
RxJava2 |
データソースの非同期での取得 |
最後に
弊社ではいっしょに働くアプリエンジニアを募集しています!
ご興味のある方はぜひご応募いただけますと嬉しいです。
Androidエンジニア募集中!: www.wantedly.com
リードエンジニア(Android、iOS問わず)も募集中!:www.wantedly.com