DEV Community

kaede
kaede

Posted on • Updated on

Android 基礎 -- Part 06 Composable から ViewModel を経由して Repository の save 関数を 呼び出せるようにする

事前準備


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

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

参考

implementation 
'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1'
Enter fullscreen mode Exit fullscreen mode

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 を作成する。

package com.example.hellojetpack.screen.edit

class EditNoteViewModel {
}
Enter fullscreen mode Exit fullscreen mode

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



ViewModel の振る舞いを作成


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

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

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

class EditNoteViewModel(app:Application): AndroidViewModel(app){
}
Enter fullscreen mode Exit fullscreen mode

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

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

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


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

class EditNoteViewModel(
    app:Application
): AndroidViewModel(app){
    val repo: NoteRepository = DatastoreNoteRepository(app)
}
Enter fullscreen mode Exit fullscreen mode

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



Composable から ViewModel を呼ぶ


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

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

@Composable
fun EditScreen(viewModel: EditNoteViewModel = viewModel() 
  ) {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

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

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

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

@Composable
fun NoteApp() {
    EditScreen()
}
Enter fullscreen mode Exit fullscreen mode

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

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

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


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

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)
                        )
                    }
                }
            )
        }
    ) 
// ...
Enter fullscreen mode Exit fullscreen mode

Scaffold > TopAppBar > actions > IconButton >

とあって、Iconbutton の onClick に

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

class EditNoteViewModel(
    app:Application
): AndroidViewModel(app){
    fun save(body: String) {
        TODO("Not yet implemented")
    }
}
Enter fullscreen mode Exit fullscreen mode

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



動作確認


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

Image description

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


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

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

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

    val repo: NoteRepository = DatastoreNoteRepository(app)
}
Enter fullscreen mode Exit fullscreen mode

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

save の中身を実装する。

        if (body.trim().isEmpty()) {
            println("ViewModel/save/ NO TEXT")
            return
        }
Enter fullscreen mode Exit fullscreen mode

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

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

Enter fullscreen mode Exit fullscreen mode

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

Image description

Image description

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

Image description

Image description

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

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

Top comments (0)