事前準備
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)