事前準備
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'
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 { }
とりあえず中身は空で作る。
ViewModel の振る舞いを作成
EditNoteViewModel クラスを使う時に必要なものを作成する。
https://youtu.be/961Bd4UQOl0?t=87
import android.app.Application import androidx.lifecycle.AndroidViewModel class EditNoteViewModel(app:Application): AndroidViewModel(app){ }
AndroidViewModel と言う、
Android の ViewModel クラスから拡張する。
プライマリーコンストラクターとして、
Application 型の app を必ず受け取るようにする。
この app は謎。
そのページのルートコンポーネントだと仮定。
EditNoteViewModel クラスで、前回作ったコンテキストに保存する DataStoreRepository のインスタンスを作る
class EditNoteViewModel( app:Application ): AndroidViewModel(app){ val repo: NoteRepository = DatastoreNoteRepository(app) }
これでクリーンアーキテクチャの Driver 相当の
Repository のインスタンスを作成する。
ここで app が必要となるようだ。
Composable から ViewModel を呼ぶ
EditNoteScreen の呼び出し時に EditNoteViewModel をプライマリーコンストラクタとして、ライブラリから取ってくる
https://youtu.be/961Bd4UQOl0?t=136
@Composable fun EditScreen(viewModel: EditNoteViewModel = viewModel() ) { // ... }
Component 相当の screen/edit/EditNoteScreen.kt
ここを編集して先ほど作った EditNoteViewModel が呼ばれるようにする。
ViewModel で必要になる App は、下記のフローでライブラリが謎の挙動で取ってきてくれるものと予想を立てた。
EditScreen 関数が呼ばれるとき、
ライブラリから viewModel() を実行してインスタンスが作成されようとする。
EditNoteViewModel が型として指定されているのを発見する。
EditNoteViewModel を見て、プライマリーコンストラクタで
Application が必要なのを発見する。
Application をライブラリが謎の挙動で取ってきながら、EditNoteViewModel 型で viewModel を作成して変数に入れる
@Composable fun NoteApp() { EditScreen() }
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) ) } } ) } ) // ...
Scaffold > TopAppBar > actions > IconButton >
とあって、Iconbutton の onClick に
viewModel.save(body) を入れる。
body はフォームで使うステートだ。
class EditNoteViewModel( app:Application ): AndroidViewModel(app){ fun save(body: String) { TODO("Not yet implemented") } }
ここで Alt Enter するとこのように TODO で生成できる。
動作確認
画面から実行して ViewModel の save を呼び出せるのを確認
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) }
先ほどこうやって EditNoteViewModel の save が TODO で生成できた。
save の中身を実装する。
if (body.trim().isEmpty()) { println("ViewModel/save/ NO TEXT") return }
まず、中身がない時に処理を中止するようにする。
viewModelScope.launch { try { repo.save(body) println("ViewModel/save/ after try") } catch (e: Exception) { println(e) } }
次に viewModelScope を使って非同期で try/catch を動かす。
この中で レポジトリの save メソッドを動かす。
すると、中身を入れた時は通常にコンソールに出力されて
中身がない時は NO TEXT のメッセージのみが出た。
なお、処理の成否はここでは検出できない。
Top comments (0)