MENU

【Jetpack Compose】ViewModelの使い方

記事内に商品プロモーションが含まれる場合があります
目次

ViewModelの役割

ViewModelは、UIの状態を保存し、ビジネスロジックとUIの間でデータのやり取りを担当します。

Jetpack ComposeでViewModelを使うことで、UIの状態を保持し、画面の状態が変化してもデータが失われないようになります。

ViewModelがないと・・・

例えば、次のシンプルなカウンターアプリを見てみましょう。

これは以前紹介したmutableStateを使って変数の状態を管理しています。

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--
    }
}

remember 関数は、Composable 関数内で使用されるものであるので、ViewModel クラス内で使用することはできません。

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")
            }
        }
    }
}

これで、プログラムを動かしてみてください。

画面の向きが変わってもカウント数が変わらないことが確認できるはずです。

Share

Comment

コメントする

目次