ViewModelのこと

ViewModelのことがわかってきたので、忘れないよう記録しておく

:::note info
クリックするたびボタンのテキストがA→B→Aのように変わるボタンを実装
:::

## ViewModelを使わない場合
MainActivityクラスの下にOtameshiApp関数を追加。
（既存のGreetingなんちゃらの中に記述してもいいけど！）

Column設定して（面倒ならmodifierはいらない）、ボタンを1つ設定。

ボタンのテキスト用の変数`buttonText`を宣言。
ボタンの`onClick`に直接ボタンのテキストを変更する処理を記述

~~~kotlin:MainActivity.kt
@Composable
fun OtameshiApp(modifier: Modifier = Modifier) {
    Column(
        modifier
            .fillMaxSize()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        var buttonText by remember { mutableStateOf("A") }
        Button(
            onClick = { buttonText = if (buttonText == "A") "B" else "A" },
            modifier= Modifier.size(width= 200.dp, height = 50.dp)
        ) {
            Text(text = buttonText,fontSize=30.sp)
        }
    }
}
~~~

これを実行して、ボタンをポチポチすると、A→B→A→Bとボタンのテキストが変わる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3556392/68e96913-28cb-4e29-8e84-21e6fb4bf8f4.png)

ボタンをBにしたまま、画面を回転させると・・・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3556392/6921dc4f-f779-49e0-aca1-b4d8708ef34f.png)
Aに戻る不思議:sweat_smile:

この問題は変数宣言時に'remember'ではなく
`var buttonText by rememberSaveable { mutableStateOf("A") }`とすると解決するが、根本解決にはなってない。
なぜならこの画面でボタンをBにしてから、別の画面に移動し、再度この画面に戻ってきたとき、Aボタンに戻ってしまうから。
（別の画面肉と値が保持されないので）

他にも、同じボタンをこの画面と別画面で共有したくても、画面ごとでしか状態が保持できないので一貫性を保つのが難しい。

あと、テストでも`remember {mutableStateOf("A"}`を直接使うことができないので、状態のテストがやりづらい。

:::note info
これらの問題がViewModelを使うと全部解決できてしまう！:sparkles:
:::
## ViewModelを使う場合のコード例

### 1.gradleファイルに依存関係を追加
なんか、implementationの書き方が変わった。
簡単になった！°˖☆◝(⁰▿⁰)◜☆˖°
```kotlin:build.gradle.kts(Module:app)
dependencies {
    //割愛
    implementation(libs.lifecycle.viewmodel.compose)
    //割愛
}
```
追加したら、**SyncNow**する。
ほんと、よく忘れるので、やる。絶対。

### 2.画面と分離させるため、新しいKotolinファイルを作る
model用のパッケージ作って、その中にファイル作るのが正しいと思うけど、テスト的に作ってるアプリなので、いったん別ファイルにしておけばOKということで！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3556392/f6b3a6b4-f242-4f55-add8-94eefd923f45.png)

### 3.状態を管理するクラスを作る
 1個しか管理する項目がないのに作るんかーい！って気持ちになったけど、増えるかもしれないから。
```kotlin:OtameshiViewModel.kt
data class OtameshiUiState(
    val buttonText:String = "A"
)
```

### 4.ViewModelのクラスを作る
とりあえず、おまじないのように以下を記述
```kotlin:OtameshiViewModel.kt
class OtameshiViewModel:ViewModel() {
    private val _uiState = MutableStateFlow(OtameshiUiState())
    val uiState: StateFlow<OtameshiUiState> = _uiState.asStateFlow()

    //AボタンをBボタンに変更する処理
    fun changeButtonText() {
        _uiState.update {
            it.copy(buttonText = if (it.buttonText == "A") "B" else "A")
        }
    }
}
```
1行目の`uiState`は、`MutableStateFlow`なので内容を変更できるし、変更したら画面側に通知する仕組みになっている。
ただし、外部から不用意に値を変えられるといけないので、このクラス内でしか値が変えられないようにしている。
外部用には2行目の読み取り専用の`uiState`を使う。

その下の`changeButtonText()`メソッドは、ボタンをクリックしたときにボタンのテキストがAだったらBへ、BだったらAに変更する処理

### 5.画面側にViewModelを設定
一番最初に作ったOtameshiAppをviewModelとアクセスできるよう変更していく。
```kotlin:MainActivity.kt
@Composable
fun OtameshiApp(
    modifier: Modifier = Modifier,
    viewModel: OtameshiViewModel = viewModel()
) {
    Column(
        modifier
            .fillMaxSize()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        val uiState by viewModel.uiState.collectAsState()
        Button(
            onClick = { viewModel.changeButtonText()},
            modifier= Modifier.size(width= 200.dp, height = 50.dp)
        ) {
            Text(text = uiState.buttonText,fontSize=30.sp)
        }
    }
}
```

OtammeshiApp()のかっこの中に、
`viewModel: OtameshiViewModel = viewModel()`を追加。
これで画面からOtameshiViewModelクラスが使えるようになった。

次に{}の一番上で、`val uiState by viewModel.uiState.collectAsState()`を設定
これはOtameshiViewModelの中で、読み取り専用で作ったuiState変数のこと。
uiState.buttonTextにAとかBが入るので、ボタンのテキストには`uiState.buttonText`を表示するように設定。

ボタンをクリックしたときの処理`onClick`はモデルの`changeButtonText`メソッドを設定。

## 最後に
画面が1個しかないなら、viewModelは正直面倒くさい！
作る意味もわからん:ghost:
この記事を書いてしみじみ思った。
画面をたくさん作りたくなってからViewModelを勉強しても遅くないかなって。