📝

Android Basic with ComposeのUnit2で学んだことをまとめてみる - 後編(Compose state)

2024/10/30に公開

はじめに

こんにちは、某SIerでSEをやっているnekorush14です。
この記事は絶賛再*n入門しているAndroid開発について、Google公式のLearning Courseを通して学んだことをアウトプットするシリーズです。

Jetpack Composeに関するCourseのUnit2で得た知見を話します。
https://developer.android.com/courses/android-basics-compose/unit-2?hl=en

今回はUnit2の残りの部分、ComposeのState管理についてまとめてみます。

前回の記事はこちら👇
https://zenn.dev/nekorush14/articles/5b67f023cfd3bc

Compose

Jetpack ComposeのComposableは基本ステートレスです。つまり、Composableとしては状態を持ちません。
また、Composableを用いて生成されたUIはいつでも再構成される可能性があります。この再構成をReCompositionと呼びます。
ComposableはReComposition時に初期化されリセットされます。例えばText Composableに文字を表示し、ボタンを押したタイミングで変化するUIを作った場合を考えてみます。
このUIに対して、画面を回転するなどで状態が変化し、ReCompositionが発生すると、Text Composableの中身はボタンを押す前に戻ります。
これが「Composableがステートレスである」と言われる理由です。

Composableは状態の変更が発生すると影響を受ける(依存する)Composable関数を新たな状態で再実行します。
これによりUIが更新されます。

例えば以下のようなUIがあったとします。

Row {
    Text(
        text = stringResource(stringRes)
    )
}

アプリの状態が変わると、RowRowに依存するTextがReCompositionの対象となります。
Composeは初期表示時にUIを生成します。これをInitial Compositionとよびます。
Compositionが生成されるタイミングはInitial Compositionのみで、ReCompositionの際にのみ更新可能です。
つまり、ReCompositionがComposeを変更する唯一の方法であり、適切にReCompositionを行うためには追跡すべき状態変化を明らかにしておく必要があります。

そこで、Stateの管理が必要となります。

State

Jetpack Composeにおいて、状態はmutableStatestateが存在します。
mutableStateはその名の通り「可変」であるため、状態が持つ値を変更できます。一方、mutableがつかないstateはimmutableであるため、宣言時の状態から変更できず、Read onlyとなります。

Jetpack Composeでは、mutableStateを生成するためにmutableStateOf() Composableを使用します。mutableStateOf()を用いて生成された状態は追跡可能(Observable)となります。Composable関数内でmutableStateOf()により生成する状態は宣言するだけではReCompositionの際に初期化されてしまいます。つまり、状態が変化したあともどこかに状態を保持して置かなければならないということです。

Jetpack ComposeにはrememberというComposableがあり、これを用いることで状態をメモリに保持し、ReCompositionの際に初期化されることを回避します。

rememberによる状態定義の例
// Intの 1 という値を持つ状態を生成する
var result by remember { mutableStateOf(1) }

remember Composableで計算された値はInitial Composition中でメモリに保存され、ReCompositionの際に使用されます。これにより、rememberを使用しなかった場合と比較し、状態が初期化されなくなります。通常、Composeの状態が適切に反映されるよう、remembermutableStateOfは一緒に使用されます。

remember Composableを用いたStateを複数のComposableで使用する場合、上位のComposableに移動していきます。これをState Hoistingと呼びます。
使用するComposableには引数またはLambdaとして渡して使用します。Object自体は参照渡しのため、Stateが変わった場合でもtate Hoisting前と変わらずReComposition時にUIが更新されます。

state hoistingの例
@Composable
fun SampleComposable(modifier = Modifier) {
    // ここで状態を宣言する
    var isSampleState by remember { mutableStateOf(true) }

    Row (
      modifier = modifier.clickable { isSampleState = !isSampleState }
    ) {
        CustomText(isSampleState)
    }
}

@Composable
fun CustomText(
  isSampleState = false,
  modifier = Modifier
) {
    // ここで状態を宣言しない

    Text(
      text = "$isSampleState"
    )
}

前述の通り、あるComposableで変更したStateを別のComposableで使用したい場合はState Hoistingの考え方で上位のComposableにHoistする必要があります。これにより、State-fullなComposableとState-lessなComposableが存在することとなります。

State-fullなComposableは文字通り状態を保持しているComposableです。このComposableはrememberなどで状態を保持しているComposableであり、時間の経過とともに変化する可能性のある状態の一部を保持するComposableです。一方、State-lessなComposableは文字通り状態を持たないComposableで、新たなStateを保持・定義・更新しないComposableのことです。

State Hoistingした場合、たいていはvalue:TonValueChange: (T) -> Unitをパラメータとして追加します。
value: Tは現在の値を示すパラメータ、onValueChange: (T) -> UnitはコールバックのLambdaです。このLambdaはユーザーインタラクションにより値が更新されたときに状態を他の場所で更新できるようにするトリガーです。
また、補足ですが、modifierはすべてのComposable関数にデフォルトパラメータとしてModifierを提供することがベストプラクティスとなります。これにより再利用性が向上します。modifierはすべての必須パラメータの最後に定義してオプショナルパラメータとします。

Out of scopeの話

このUnitのスコープ外ですが、Unit3の最終課題に取り組む中で発生した問題とその解決策についてまとめてみます。
前述の通り画面回転したときReCompositionが発生します。このとき、rememberではStateが初期化されてしまいます。
そこで、rememberではなくrememberSaveableを使用するを使用することで画面回転などのConfiguration Changeが発生した場合でも、状態を保持できます。

rememberSavableの例
// rememberの代わりにrememberSaveableを使用する
var imageResourceId by rememberSaveable { mutableIntStateOf(0) }

上記のサンプルの通り、rememberSaveableであってもmutableStateOf()も使用できます。ただし、rememberSaveableはすべてのTypeが使用できるわけではなく、独自クラスを使用している場合は自分のクラスをParcelableにするかSaverを実装する必要があります。

まとめ

今回はUnit2で扱われたComposeのState管理についてまとめてみました。

Jetpack Composeの状態管理は標準で用意されており、かつわかりやすい定義の仕方だと感じました。
また、どのようなタイミングでReCompositionが起きるかも常に意識する必要があることを学びました。

次回はUnit3です。Unit3では、スクロール可能なリストの定義、Material Designが扱われましたのでそれぞれについてまとめます。

参考資料

Discussion