今回はJetpack ComposeとViewModelの連携方法を説明していきます。ViewModelはMVVMアーキテクチャでViewとModelを結びつける役割を担います。Jetpack ComposeアプリにおけるViewは、もちろんコンポーザブルで作成します。そこで今回は、コンポーザブルからViewModelにアクセスする方法を確認していきます。
目次
ViewModelの役割
AndroidアプリにおけるViewModelの役割は、従来のViewシステムでもJetpack Composeでも同じです。ささっと確認しておきましょう。
- ViewとModelを接続する
- 従来のViewをコンポーザブルに置き換えても、ModelとViewModelは基本的に従来と同じものを使えます。ViewModelは、View階層のコンポーザブルとModelを結び付けるために存在します。コンポーザブルの操作を受けてModelを更新する、View→Modelの方向と、Modelが更新されたときにViewの表示を更新する、Model→Viewの方向とがあります。
- Viewのライフサイクルを超えてデータを保持する
- Jetpack Composeのライフサイクルは従来のViewやFragmentに比べてシンプルですが、それでもActivityのライフサイクルからは逃れられません。画面の向きを変えたときなど、Activityが再生成されるとコンポーザブルが保持している変数も初期化されてしまいます。
rememberSaveable
を使ってコンポーザブル内でデータを保持することもできますが、ViewModelでデータを保持した方が簡単です。
- Jetpack Composeのライフサイクルは従来のViewやFragmentに比べてシンプルですが、それでもActivityのライフサイクルからは逃れられません。画面の向きを変えたときなど、Activityが再生成されるとコンポーザブルが保持している変数も初期化されてしまいます。
準備
最初に必要な依存関係をbuild.gradle(:app)
に追加しておきます。
dependencies {
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.4.0"
implementation "androidx.compose.runtime:runtime-livedata:1.0.5
lifecycle-viewmodel-compose
はコンポーザブル内でViewModelのインスタンスを取得するために使います。最新バージョンはこちらで確認できます。少し前まではアルファバージョンでしたが、2021年10月に正式版になり、androidx.lifecycle
パッケージと同じバージョンの2.4.0になりました。
runtime-livedata
は、コンポーザブル内でViewModelのパラメータの変化を監視するために使います。最新バージョンはこちらで確認できます。この記事の執筆時点では1.0.5が最新です。androidx.composeパッケージは複数のパッケージから構成されているので、他のパッケージとバージョンをそろえておいたほうが無難だと思います。
ViewModelの作成
ViewModel自体の実装は、Jetpack Composeを使わない場合と変わりません。今回は下記のように数値を一つだけ保持する簡単なViewModelを用意します。
class SampleViewModel: ViewModel() {
val count = MutableLiveData(0)
fun countUp() {
val c = count.value ?: 0
count.value = c + 1
}
}
唯一のパラメータのcount
はMutableLiveData
で実装しています。countUp()
を呼び出すと、count
がインクリメントされます。本来はViewModelはModelを参照するのですが、今回は簡単のためModelは省略します。
ViewModelを取得
コンポーザブル関数でViewModelオブジェクトを取得するには、androidx.lifecycle.viewmodel.compose
パッケージのviewModel()
が使えます。viewModel()
は、クラスが一致するViewModelのオブジェクトを返します。初めて呼び出したときは新しくインスタンスを作成し、すでにインスタンス作成済みの場合は同じインスタンスを返します。これについてはこの記事の最後で補足します。
コンポーザブル関数内で使うViewModel変数は、関数の引数として定義し、そのデフォルト値としてviewModel()で取得したインスタンスを渡すのが定石です。こうすることによって、呼び出し側の記述をシンプルにしつつ、必要な時は別のインスタンスを指定することが可能になります。
@Composable
fun AppScreen(viewModel: SampleViewModel = viewModel()) {
...
}
この例ではviewModel
変数に先ほど作成したSampleViewModel
のインスタンスが渡されます。
ViewModelのパラメータの監視
ViewModelのパラメータの変更を監視するには、observeAsState()
を使います。このAPIによって、LiveData
をState
として扱うことができます。つまり、LiveData
の値が変更されたことをコンポーザブルが検出し、UIの再構築が行われます。その結果、LiveData
の値の変更が画面表示に反映されるのです。
@Composable
fun AppScreen(viewModel: SampleViewModel = viewModel()) {
val count: Int by viewModel.count.observeAsState(0)
Column {
Text(text = "Count: $count")
Button(onClick = {viewModel.countUp()}) {
Text(text = "Count up")
}
}
}
この例では、先ほど作成したSampleViewModel
のcount
を状態変数として取得しています。これによって、SampleViewModel#count
の値が変更されると画面が再構築され、Text
の文字列表示が更新されます。なお、observeAsState()
の引数は、LiveDataが初期化されていないときに使われる初期値ですが、今回はSampleViewModel#count
側にも初期値を設定しているので、observeAsState()
で設定した初期値が表示されることはありません。
count
更新のトリガになるのはButton
のonClick
です。onClick
のコールバック内でSampleViewModel#countUp()
を呼び出しているので、ボタンをクリックするとcount
が更新され、画面が更新されるという流れになっています。
コンポーザブルの表示
先ほど作成したAppScreen()
コンポーザブル関数を呼び出す側では、何も特別な処理は必要ありません。ViewModelはデフォルト値が設定されているので、呼び出し側では単にAppScreen()
を引数なしで呼び出すだけです。
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AppScreen()
}
}
}
結果
サンプルの動作は以下のようになります。なお、コンポーネントの配置はModifier
などで調整していますので、上に記載のソースコードと全く同じではありません。Modifier
やColumn
の配置について詳しく知りたい方は、基本編の「コンポーネントを装飾する」や「コンポーネントを配置する」をご覧ください。
補足:複数のコンポーザブルからViewModelを利用する
先に説明した通り、 androidx.lifecycle.viewmodel.compose
パッケージのviewModel()
関数を使うと、すでに対象のViewModelがインスタンス化されている場合は、そのインスタンスを返してくれます。したがって、複数のコンポーザブルから一つのViewModelを利用するような場合もシンプルにソースコードを記述できます。
例えば上のAppScreen()
では一つのコンポーザブル関数内にText
とButton
を配置していましたが、これを二つのコンポーザブルに分割し、それぞれのコンポーザブル内でViewModelを取得できます。下記のようにソースコードを変更しても、動作は同じです。
@Composable
fun AppScreen() {
Column {
CountText()
CountButton()
}
}
@Composable
fun CountText(viewModel: SampleViewModel = viewModel()) {
val count: Int by viewModel.count.observeAsState(0)
Text(text = "Count: $count")
}
@Composable
fun CountButton(viewModel: SampleViewModel = viewModel()) {
Button(onClick = {viewModel.countUp()}) {
Text(text = "Count up")
}
}
まとめ
今回は、コンポーザブル関数からViewModelを利用する方法を学びました。ViewModelを取得するのに便利なviewModel()
と、LiveData
をState
として扱うことでコンポーザブルを表示を更新するときに使うobserveAsState()
はぜひ使いこなしてください。
Jetpack Compose入門