Android 基礎 -- Part 06 Composable から　ViewModel を経由して Repository の save 関数を 呼び出せるようにする
Tags: android, jetpackcompose
Published: 2023-01-20 14:33:06


# 事前準備

---

## lifecycle-viewmodel-compose のライブラリを Gradle 管理に追加

https://www.youtube.com/watch?v=961Bd4UQOl0&list=PLgAI_5b_7BdRaBPAD5RMrzjoSwefDXpkw&index=9

参考

```kotlin
implementation 
'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1'
```

app の build.gradle に android x の lifecycle から
viewmodel-compose の　2.5.1 をインポートする。

---

---

# ViewModel のファイルを作成

---

## screen/edit/ に EditNoteViewModel.kt を作成

ViewModel はコンテキストというグローバルステートのことだと解釈して進める。

screen は React などでの Component フォルダ相当。

screen/ パッケージ の edit/ 機能パッケージに
現在編集画面コンポーザブルとして
EditNoteScreen.kt が入っている。

ここに　EditNoteViewModel という名前で
ViewModel を作成する。

Angular Dart は 1 つの Atoms を作るときに
button.{html,js,css} と各ファイルを作る。
これと同じ思想だと解釈した。

EditNoteViewModel.kt を作成する。

```kotlin
package com.example.hellojetpack.screen.edit

class EditNoteViewModel {
}
```

とりあえず中身は空で作る。

---

---

# ViewModel の振る舞いを作成

---

## EditNoteViewModel クラスを使う時に必要なものを作成する。

https://youtu.be/961Bd4UQOl0?t=87

```kotlin
import android.app.Application
import androidx.lifecycle.AndroidViewModel

class EditNoteViewModel(app:Application): AndroidViewModel(app){
}
```

AndroidViewModel と言う、
Android の ViewModel クラスから拡張する。

プライマリーコンストラクターとして、
Application 型の app を必ず受け取るようにする。

この app は謎。
そのページのルートコンポーネントだと仮定。


---

## EditNoteViewModel クラスで、前回作ったコンテキストに保存する　DataStoreRepository のインスタンスを作る

```kotlin
class EditNoteViewModel(
    app:Application
): AndroidViewModel(app){
    val repo: NoteRepository = DatastoreNoteRepository(app)
}
```

これでクリーンアーキテクチャの Driver 相当の 
Repository のインスタンスを作成する。
ここで　app が必要となるようだ。

---
---

# Composable から ViewModel を呼ぶ

---

## EditNoteScreen の呼び出し時に EditNoteViewModel をプライマリーコンストラクタとして、ライブラリから取ってくる

https://youtu.be/961Bd4UQOl0?t=136

```kotlin
@Composable
fun EditScreen(viewModel: EditNoteViewModel = viewModel() 
  ) {
  // ...
}
```

Component 相当の screen/edit/EditNoteScreen.kt
ここを編集して先ほど作った EditNoteViewModel が呼ばれるようにする。

ViewModel で必要になる App は、下記のフローでライブラリが謎の挙動で取ってきてくれるものと予想を立てた。

EditScreen 関数が呼ばれるとき、
ライブラリから viewModel() を実行してインスタンスが作成されようとする。
EditNoteViewModel が型として指定されているのを発見する。
EditNoteViewModel を見て、プライマリーコンストラクタで
Application が必要なのを発見する。
Application をライブラリが謎の挙動で取ってきながら、EditNoteViewModel 型で viewModel を作成して変数に入れる




```kotlin
@Composable
fun NoteApp() {
    EditScreen()
}
```

EditNoteScreen　は Component 相当なので
NoteApp と言う Cotainer 相当の Composable で呼ばれる。

MainActivity (Root) 
-> NoteApp (Container)
-> EditNoteScreen (Component)
-> EditNoteViewModel (ViewModel)

と言う呼び出しの順番になっている。

---

## Composable の EditNoteScreen の onClick で EditNoteViewModel の save が呼ばれるようにする

```kotlin
fun EditScreen(viewModel: EditNoteViewModel = viewModel() ) {
    var body by remember { mutableStateOf("") }
    Scaffold(
        topBar = {
            TopAppBar(
                title = {
                    Text(text = "NoteApp")
                },
                actions = {
                    IconButton(onClick = {
                        println(body)
                        viewModel.save(body)
                    }) {
                        Icon(
                           Icons.Default.Done,
                           contentDescription = stringResource(id = R.string.app_name)
                        )
                    }
                }
            )
        }
    ) 
// ...
```

Scaffold > TopAppBar > actions > IconButton > 

とあって、Iconbutton の onClick に

viewModel.save(body) を入れる。
body はフォームで使うステートだ。

```kotlin
class EditNoteViewModel(
    app:Application
): AndroidViewModel(app){
    fun save(body: String) {
        TODO("Not yet implemented")
    }
}
```

ここで　Alt Enter するとこのように TODO で生成できる。

---
---

# 動作確認

---

## 画面から実行して ViewModel の　save を呼び出せるのを確認

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/osh52ket6z3agl33kkec.png)

TODO で作っているので、無事に un implement error が出た。

---

##　ViewModel の　save メソッドで Repository の save を呼ぶようにする

https://youtu.be/o74QgfQ-fIM?t=98

```kotlin
class EditNoteViewModel(
    app:Application
): AndroidViewModel(app){
    fun save(body: String) {
        TODO("Not yet implemented")
    }

    val repo: NoteRepository = DatastoreNoteRepository(app)
}
```

先ほどこうやって EditNoteViewModel の save が TODO で生成できた。

save の中身を実装する。

```kotlin
        if (body.trim().isEmpty()) {
            println("ViewModel/save/ NO TEXT")
            return
        }
```

まず、中身がない時に処理を中止するようにする。

```kotlin
        viewModelScope.launch {
            try {
                repo.save(body)
                println("ViewModel/save/ after try")
            } catch (e: Exception) {
                println(e)
            }
        }

```

次に viewModelScope を使って非同期で try/catch を動かす。
この中で レポジトリの　save メソッドを動かす。

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ctzsairmmyskbl907xez.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z8s30fb17o4mfm1kun6g.png)

すると、中身を入れた時は通常にコンソールに出力されて

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/brqsgcriaxcgfk22mhtj.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/20nio2judfmyg3shgl2v.png)

中身がない時は NO TEXT のメッセージのみが出た。

なお、処理の成否はここでは検出できない。