LoginSignup
7
3

More than 1 year has passed since last update.

【Jetpack Compose】 Molecule を使って Flow → StateFlow に変換する

Last updated at Posted at 2021-12-19

はじめに

本記事は 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に公開しているので、併せてご参照ください。
7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3