はじめに
本記事は with Advent Calendar 2021 20日目の記事です。
こんにちは with でAndroid エンジニアをしている石田です。Jetpack Compose がプロダクトで使えるようになって以来、 「状態」 を扱うことが多くなったように感じる今日このごろです。
というわけで今回は状態にまつわるお話です。
Composeで状態を扱う時に困ること
状態を扱うとき、ViewModel では StateFlow<T>
、 Compose では State<T>
を使用します。 StateFlow<T>
は collectAsState
を使って 簡単に State<T>
に変換することができます
困るのは Flow<T>
を State<T>
に変換したい場合です。 StateFlow<T>
と同様に collectAsState
を使って変換できますが、 その際に初期値を渡す必要があります。 言うまでもなく、Flow<T>
には初期値というものが存在していないためです。
適当な初期値を渡せば一応は解決ですが、View側で初期状態を制御し、Flowでそれ以降の状態を制御しているという状況は少し気持ちが悪い感じがします。
また、複数の Flow から1つの状態を作る場合はどうでしょうか。 Flow の combine
用いて 1つの Flow にまとめるという処理を書くことになりますが、入力の Flow の数が多ければ多いほど複雑なコードになりがちですが。
そこで Molecule
これらの問題の解決策として、Cash が公開している Molecule というライブラリの存在を知ったので 簡単に紹介したいと思います。
Molecule のサンプル
下記の ViewState
というモデルを Compose 側に公開するとします。 season
food
color
はそれぞれ独立した3つの Flow<String>
がソースであり、 値が変化するという想定です。
sealed class ViewState {
object Loading : ViewState()
data class Data(
val season: String,
val food: String,
val color: String
) : ViewState()
}
3つの Flow から ViewState
に変換するコードを書きます。 Molecule のスゴいところはこれをComposeで書けるところです。 比較的スッキリと書けているのではないでしょうか。
@Composable
fun buildViewState(
seasonFlow: Flow<String>,
foodFlow: Flow<String>,
colorFlow: Flow<String>
): ViewState {
val season by seasonFlow.collectAsState(null)
val food by foodFlow.collectAsState(null)
val color by colorFlow.collectAsState(null)
return if (season == null && food == null && color == null) {
ViewState.Loading
} else {
ViewState.Data(
season = season ?: "N/A",
food = food ?: "N/A",
color = color ?: "N/A"
)
}
}
次に 先程の buildViewState
を使って3つの Flow から StateFlow<ViewState>
を作成するコードを書きます。 CoroutineScope.launchMolecule
というAPIを使います
private val seasonFlow: Flow<String> = ...
private val foodFlow: Flow<String> = ...
private val colorFlow: Flow<String> = ...
val state: StateFlow<ViewState> = moleculeScope.launchMolecule {
buildViewState(seasonFlow, foodFlow, colorFlow)
}
これで StateFlow を作ることができました。あとはいつも通りで Compose で collectAsState
を使い State<ViewState>
に変換します。
val state by viewModel.state.collectAsState()
MainScreen(state)
まとめ
- Molecule を使えば Compose で StateFlow を作ることができます。
- 複数の Flow から1つの状態を作りたい場合においてコードをスッキリさせることができるだけでなく、状態を1箇所で制御することができるためテストも容易です。
- 複雑な画面であればあるほど威力を発揮する良ライブラリだと思いました。
- 動作可能なサンプルコードをGitHubに公開しているので、併せてご参照ください。