目次
ViewModelの役割
ViewModelは、UIの状態を保存し、ビジネスロジックとUIの間でデータのやり取りを担当します。
Jetpack ComposeでViewModelを使うことで、UIの状態を保持し、画面の状態が変化してもデータが失われないようになります。
ViewModelがないと・・・
例えば、次のシンプルなカウンターアプリを見てみましょう。
これは以前紹介したmutableState
を使って変数の状態を管理しています。
【Jetpack Compose】mutableStateで状態の変化を扱う | 値が更新されたときに画面も更新する | Seeds
JetPack Composeで次のようなボタンをカウント回数を表示するコードを準備しました。 期待する動きとしては、カウントをタップすると数字が0から順番に増えていってほしい…
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.naoyaono.jetpack_compose_ui.ui.theme.Jetpack_Compose_UITheme
@Composable
fun CounterApp() {
var count by remember { mutableStateOf(0) }
fun increment(){
count++
}
fun decrement(){
count--
}
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "カウント:${count}",
fontSize = 36.sp,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(15.dp))
Row {
Button(
modifier = Modifier.width(100.dp),
onClick = { increment() }
) {
Text(text = "+1")
}
Spacer(modifier = Modifier.width(20.dp))
Button(
modifier = Modifier.width(100.dp),
onClick = { decrement() }
) {
Text(text = "-1")
}
}
}
}
@Preview(showBackground = true)
@Composable
fun TheCounterAppPreview() {
Jetpack_Compose_UITheme {
TheCounterApp()
}
}
プログラムを実行すると、+1をタップすればカウントが増え、-1をタップすればカウントが減ります。
しかし、カウント中に画面を横向きにするとどうなるでしょうか?
次のように状態がリセットされ、カウントが0に戻ってしまいます。
これでは、ユーザーから不満の声が出てしまいますし、アプリの不具合と捉えられてしまいます。
この問題を解決するには、例えば画面の向きを縦向きだけに固定するといったやり方もありますが、ViewModelを使うことで、縦横画面の向きを回転させても変数の状態を保持することができます。
ViewModelの作成
ViewModelを作成するには、ViewModel
クラスを継承したクラスを作成します。
import androidx.lifecycle.ViewModel
class CounterViewModel : ViewModel() {
}
この例ではクラス名をCounterViewModel
としています。
次に、ViewModel内でデータを保持するための変数や関数を定義します。
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
class CounterViewModel:ViewModel() {
// カウント数を保持する変数(このクラス内で利用)
private val _count = mutableStateOf(0)
// 外部に表示するときに使う変数
var count: MutableState<Int> = _count
// カウント数+1する関数
fun increment(){
_count.value++
}
// カウント数を-1する関数
fun decrement(){
_count.value--
}
}
ViewModelを使う
作成したViewModelを使っていきましょう。
ViewModelのインスタンス化する
MainActivity内でViewModelをインスタンス化し、CounterAppに渡します。
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel
import com.naoyaono.jetpack_compose_ui.ui.theme.Jetpack_Compose_UITheme
import com.naoyaono.jetpack_compose_ui.viewmodel.CounterViewModel
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// 追加:ViewModelのインスタンス化
var viewModel: CounterViewModel = viewModel()
Jetpack_Compose_Sample {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
CounterApp(viewModel)
}
}
}
}
}
CounterApp内でViewModelの使用する
冒頭のコードをViewModelを使ったコードに書き換えます。
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
fun CounterApp(viewModel: CounterViewModel) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "カウント:${viewModel.count.value}", // ViewModelから値を取り出す
fontSize = 36.sp,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(15.dp))
Row {
Button(
modifier = Modifier.width(100.dp),
onClick = { viewModel.increment() } // ViewModel内の関数を呼び出す
) {
Text(text = "+1")
}
Spacer(modifier = Modifier.width(20.dp))
Button(
modifier = Modifier.width(100.dp),
onClick = { viewModel.decrement() } // ViewModel内の関数を呼び出す
) {
Text(text = "-1")
}
}
}
}
これで、プログラムを動かしてみてください。
画面の向きが変わってもカウント数が変わらないことが確認できるはずです。
Comment