【Jetpack Compose】変数宣言の方法とViewを更新する方法

## 概要
Jetpack Compose を使う上で基本となる、 View の状態を表す変数の宣言方法とそれを使った View の更新方法について説明します。

詳しい仕組みについては他の方が書かれていますので、あくまで個人的な備忘録として残したいと思います。

## 環境
Android Studio version: Android Studio Dolphin 2021.3.1 Patch 1
kotlin version: 1.7.10
compose version: 1.3.1

## 内容
Jetpack Compose を使って作成した View は Compose によってその状態を常に監視されており、状態が変化すると変化した部分を再描画する処理が行われます。
これを再コンポーズといい、Jetpack Compose で作成した View を更新する手段となっています。

View の状態監視には ```State``` 型または ```MutableState``` 型の変数が用いられます。

この変数の宣言方法は大きく分けて３パターン存在し、定義方法に応じて View を更新する処理の書き方も異なります。

これらのパターンについて、ボタンをクリックしてテキストを更新するサンプルを用いて説明します。

アプリの見た目はこんな感じです。
![Screenshot_20221128_115312_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1261074/b18c9f60-c942-243a-8b1a-a5b4cc0d7103.png)



### パターン１: 「=」で変数を宣言

``` MainActivity.kt
@Composable
fun Greeting() {
    // 「=」で変数を宣言
    val message = remember {
        mutableStateOf("")
    }
    Column() {
        Text(text = "Hello ${message.value}")
        Button(onClick = {
            // コールバック関数から更新
            message.value = "world from Android"
        }) {
            Text("Button1")
        }
        Button(onClick = {
            // オリジナルの関数から更新
            changeText(message)
        }) {
            Text("Button2")
        }

    }
}

private fun changeText(message: MutableState<String>) {
    message.value = "world from Compose"
}
```
View の状態を表す変数を「=」で宣言するパターンです。
今回は ```message``` という名前の変数を用います。
これは View に表示するテキストの一部です。
ここに ```mutableStateOf``` 関数で作成した ```MutableState<String>``` 型のデータを代入します。
直接代入せずに ```remember``` 関数を用いることで変数の値がメモリに保存され、再コンポーズ時に更新後の値と保存した値との差分があれば新しい値に置き換えられます。

また、View 更新するには変数の ```value``` プロパティを更新します。

``` MainActivity.kt
message.value = "world from Android"
```

この更新処理は ```onClick``` などのコールバック関数から行えるほか、そこからさらに自分で定義した関数（上のコードでは ```changeText``` 関数）を呼び出して行うこともできます。

```  MainActivity.kt
 Button(onClick = {
    // オリジナルの関数から更新
    changeText(message)
}) {
...

private fun changeText(message: MutableState<String>) {
    message.value = "world from Compose"
}
```

なお、この関数は ```Composable``` である必要はありません。


### パターン２: 「by」で変数を宣言

``` MainActivity.kt
@Composable
fun Greeting() {
// 「by」で変数を宣言
var message by remember {
    mutableStateOf("")
}
Column() {
    Text(text = "Hello $message")
    Button(onClick = {
        // コールバック関数から更新
        message = "world from Android"
    }) {
        Text("Button1")
    }
    Button(onClick = {
        // オリジナルの関数から更新
        changeText { msg -> message = msg }
    }) {
        Text("Button2")
    }

}

private fun changeText (onNext: (msg: String) -> Unit) {
    onNext("world from compose")
}
```

```by``` を用いて  ```message``` 変数を委譲プロパティとして宣言するパターンです。
この場合 ```message``` 変数は ```MutableState<String>``` 型ではなく  ```String``` 型となります。

そのため View を更新する場合は ```value``` プロパティではなく変数自体の値を更新します。

``` MainActivity.kt
message = "world from Android"
```
この方法はパターン１と比べて ```value``` プロパティを都度参照する必要がなくコードを簡略化できるというメリットがあります。

オリジナル関数を呼び出して View を更新する場合は、引数に変数の値を更新する処理を入れた関数を渡す必要があります。

``` MainActivity.kt
Button(onClick = {
    // オリジナルの関数から更新
    changeText { msg -> message = msg }
}) {
...

private fun changeText (onNext: (msg: String) -> Unit) {
    onNext("world from compose")
}
```

この場合はパターン１と比べて少し複雑になります。

### パターン３: 変数を分解宣言

``` MainActivity.kt
@Composable
fun Greeting() {
    // 分解宣言
    val (message, setMessage) = remember { mutableStateOf("") }
    Column() {
        Text(text = "Hello $message")
        Button(onClick = {
            // コールバック関数から更新
            setMessage("world from Android")
        }) {
            Text("Button1")
        }
        Button(onClick = {
            // オリジナルの関数から更新
            changeText { msg -> setMessage(msg) }
        }) {
            Text("Button2")
        }

    }
}

private fun changeText (onNext: (msg: String) -> Unit) {
    onNext("world from Compose")
}
```
変数を分解宣言する方法です。
一つ目の変数はゲッター、二つ目の変数はセッターになります。
そのため、変数の値を参照するときは ```message``` をそのまま参照し、変数の値を更新するときは ```setMessage("world from Android")``` のようにして値をセットします。

オリジナルの関数から更新するときは二つ目の変数を用いてパターン２と同じように書きます。

## まとめ
ここまで変数宣言の３つのパターンと更新方法について書きました。
公式ドキュメントによると、状況に応じて３つのパターンからわかりやすいものを選んで書けば良いとのことです。
個人的には慣れるまでは直感的に書けるパターン１を使うことが多くなりそうな気がします。

また、最後に変数の値を更新しても View が更新されないNGパターンをいくつかご紹介します。

## NGパターン

### 変数がMutableState型ではない
``` MainActivity.kt
@Composable
fun Greeting() {
    var message = ""
    Column() {
        Text(text = "Hello $message")
        Button(onClick = {
            // コールバック関数から更新
            message = "world from Android"
        }) {
            Text("Button1")
        }
        Button(onClick = {
            // オリジナルの関数から更新
            changeText { msg -> message = msg }
        }) {
            Text("Button2")
        }
    }
}
```

この場合 ```message``` 変数はただの ```String``` 型であるため Compose によって監視されず、再コンポーズの際に差分のチェックが行われません。

なお、以下のように ```remember``` 関数を用いて定義した場合も同様です。

``` MainActivity.kt
@Composable
fun Greeting() {
    var message = remember { "world" }
    Column() {
        Text(text = "Hello $message")
        Button(onClick = {
            // コールバック関数から更新
            message = "world from Android"
        }) {
            Text("Button1")
        }
        Button(onClick = {
            // オリジナルの関数から更新
            changeText { msg -> message = msg }
        }) {
            Text("Button2")
        }
    }
}
```

### remember 関数を使っていない
``` MainActivity.kt
@Composable
fun Greeting() {
    val message = mutableStateOf("")
    Column() {
        Text(text = "Hello ${message.value}")
        Button(onClick = {
            // コールバック関数から更新
            message.value = "world from Android"
        }) {
            Text("Button1")
        }
        Button(onClick = {
            // オリジナルの関数から更新
            changeText(message)
        }) {
            Text("Button2")
        }

    }
}
```

変数は ```MutableState``` 型ではありますが、```remember``` 関数を使っていないため再コンポーズの際に変数の値が初期化され、View の表示も変わりません。

### Composable関数のスコープ内部から呼び出している

``` MainActivity.kt
@Composable
fun Greeting() {
    // 「=」で変数を定義
    val message = remember {
        mutableStateOf("")
    }
    Column() {
        Text(message = "Hello ${message.value}")
        Button(onClick = {
            // コールバック関数から更新
            message.value = "world from Android"
        }) {
            Text("Button1")
        }
        Button(onClick = {
            // オリジナルの関数から更新
            changeText(message)
        }) {
            Text("Button2")
        }

    }
    // Composable関数内部から直接呼出し・・・(A)
    changeText2(message)
}

private fun changeText(message: MutableState<String>) {
    message.value = "world from Compose"
}

private fun changeText2 (message: MutableState<String>) {
    message.value = "world from Composable"
}
```

```Composable``` 関数のスコープ内部から変数を更新しても変数の変更は無視され、View には反映されません。
これは上のコードのように ```Composable``` 関数の内部から別の関数を呼び出して更新する場合でも同じです。((A)の部分)
これは、```Greeting``` 関数が呼ばれる→```message.value``` が更新される→再コンポジションが発生する→再度 ```Greeting``` 関数が呼ばれるという無限ループを回避するためのようです。

なお、```Button``` の ```onClick``` は ```Composable``` 関数のスコープ外であるため問題ないようです。

## 参考
[状態と Jetpack Compose  \|  Android Developers](https://developer.android.com/jetpack/compose/state?hl=ja)
[Jetpack Compose State Practices - Qiita](https://qiita.com/takahirom/items/862f3c607850ce524744)