Galapagos Tech Blog

株式会社ガラパゴスのメンバーによる技術ブログです。

Jetpack Composeの状態管理クラスを調べてみた

ガラパゴスに入社して2ヶ月弱、AIR Design for Apps事業部 Androidチームの務台です。

私は現在JetpackComposeとMVVMアーキテクチャを用いたプロジェクトに携わっていますが、ふと疑問が沸きました。

ViewModelでの状態管理って、どのクラス使うのが良いんだ?

というわけで調べてみました。

結論

StateLiveDataFlowRxJava2のいずれかを使用することができるが、 ViewModelでの状態管理には基本的にはFlowを用いると良い。

ただし、Composable内でクローズな状態管理をする、XMLのレイアウトからJetpackComposeに移行する等、 他のものを使用した方が良いケースもある。

調べたこと

以下、状態管理クラスについての調査結果になります。

使用可能なクラス

ViewのComposable関数からViewModelの状態を監視して値を更新していくためには、ViewでState<T>として取得できるクラスである必要があります。 これはAndroid Developersガイドによると

  • State
  • LiveDate
  • Flow
  • RxJava2

のいずれかが使用可能です。

この中でStateLiveDataAndroidに依存したライブラリであり、ライフサイクルに関する問題はライブラリが良きに計らってくれるので、実装者は意識する必要がなくなります。

FlowRxJava2はKotlin(Java)のライブラリで、本来は非同期処理を行うためのものですが、JetpackComposeの拡張関数により状態管理にも使用することができるようになりました。 これによって、ViewModelやModelでの状態管理を一貫してFlowまたはRxJava2を使用することができます。

各クラスの調査

状態管理に使用可能な4種類のクラスについてそれぞれ使い方や特徴等を調べてみました。

State

状態管理に使用することができるものの中で最もシンプルなクラスで、mutableStateOf()インスタンスを作成することができます。

Stateを利用した状態管理の例:

// ViewModel.kt

val messageState = mutableStateOf<String>("")

fun onChangeMessage(newValue: String) {
    messageState.value = newValue
}


// View.kt

val receivedMessageState by viewModel.messageState

TextField(
    value = receivedMessageState,
    onValueChange = viewModel::onChangeMessage,
)

onChangedMessageMutableState.valueが変更されると、その値を使用しているComposableへ通知が飛び、値が更新されます。

かなりシンプルに使うことができる分、非同期処理や複数の状態を関連させる等の複雑な動きが絡んでくると管理が難しくなりそうです。

また、View側でrememberと組み合わせて、Composableの外部に公開しない、ステートフルな状態を管理する用途に使用することもできます。

rememberと併用した例:

// View.kt

val messageState = remember { mutableStateOf<String>("") }

TextField(
    value = viewModel.messageState.value,
    onValueChange = {
        messageState.value = it
    },
)

LiveData

監視可能なデータホルダクラスで、ライフサイクルに応じた監視が可能であるため、余計な心配事を減らすことができるクラスです。

ViewModel側にLiveDataを置き、View側でobserveAsState()を呼ぶことで、ViewModelでの変更を監視してComposableへ通知を送ることができます。

LiveDataを利用した状態管理の例:

// ViewModel.kt

private val _messageState = MutableLiveData<String>("")
val messageState: LiveData<String> = _messageState

fun onChangeMessage(newValue: String) {
    _messageState.value = newValue
}


// View.kt

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を用いた状態管理の例:

// ViewModel.kt

private val _messageState = MutableStateFlow<String>("")
val messageState: StateFlow<String> = _messageState

fun onChangeMessage(newValue: String) {
    viewModelScope.launch {
        _messageState.value = newValue
    }
}


// View.kt

val receivedMessageState by viewModel.messageState.collectAsState()

TextField(
    value = receivedMessageState,
    onValueChange = viewModel::onChangeMessage,
)

今回調査した中では最も後発のクラスで、LiveDataFlowを組み合わせたような使い方が可能です。

また、以下のようにcombineを用いて関連する状態をまとめて監視することもできます。

combineを用いて二つのメッセージの合計文字数を管理する例:

// ViewModel.kt

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
    }
}


// View.kt

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 ExtensoionsJavaで実装したライブラリで、これに拡張機能を追加したRxKotlinというライブラリも存在します。

Reactive Extensionsとは、Observable<T>Observer<T>インターフェースを用いて非同期通信を行うことでデータを受け渡そう、という考えみたいですが、調べても理解が難しかったためあまり触れないでおきますごめんなさいゆるして

RxJava2では様々なユースケースで非同期処理を行うことができる機能が多く備わっているようです。

RxJava2を用いた状態管理の例:

// ViewModel.kt

private val _messageState: PublishSubject<String> = PublishSubject.create()
val messageState = _messageState

fun onChangeMessage(newValue: String) {
    viewModelScope.launch {
        _messageState.onNext(newValue)
    }
}


// View.kt

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
リードエンジニア(AndroidiOS問わず)も募集中!:www.wantedly.com