🪸

Jetpack ComposeでMVVMを使う

2023/10/23に公開

Overview

https://developer.android.com/jetpack/guide?hl=ja

Androidといえば、アーキテクチャーはMVVMを使うのが推奨されているようです。
ViewModelとは何かと言いますと

  1. MのModelは、データの管理や保存、外部との入出力を担う.
  2. VのViewは、ユーザーの操作する画面のことです。ボタンや入力フォームがあります.
  3. VMのViewModelは、ViewとModelの仲介をして、状態の変更を通知、反映させる役割を担います。

summary

では簡単なカウンターアプリを作ってMVVMを再現してみましょう!
どうやって作るかというと、まずパッケージが必要です。

こちらのサイトから追加します
https://developer.android.com/jetpack/androidx/releases/lifecycle

印をつけた行のコードだけGradleに追加すればOKです。

印をつけたGradleのファイルを開いて、下の方にパッケージを追加して右上のsynnowというボタンを押すのですけど、消えてた?
またパッケージを追加しようとしたら現れるかも?

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
}

android {
    namespace 'com.example.mycounter'
    compileSdk 33

    defaultConfig {
        applicationId "com.example.mycounter"
        minSdk 24
        targetSdk 33
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary true
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
    buildFeatures {
        compose true
    }
    composeOptions {
        kotlinCompilerExtensionVersion '1.3.2'
    }
    packagingOptions {
        resources {
            excludes += '/META-INF/{AL2.0,LGPL2.1}'
        }
    }
}

dependencies {

    implementation 'androidx.core:core-ktx:1.8.0'
    implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0')
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
    implementation 'androidx.activity:activity-compose:1.5.1'
    implementation platform('androidx.compose:compose-bom:2022.10.00')
    implementation 'androidx.compose.ui:ui'
    implementation 'androidx.compose.ui:ui-graphics'
    implementation 'androidx.compose.ui:ui-tooling-preview'
    implementation 'androidx.compose.material3:material3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
    androidTestImplementation platform('androidx.compose:compose-bom:2022.10.00')
    androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
    debugImplementation 'androidx.compose.ui:ui-tooling'
    debugImplementation 'androidx.compose.ui:ui-test-manifest'

    // 公式のパッケージを追加しないとViewModelが使えない
    // https://developer.android.com/jetpack/androidx/releases/lifecycle
    // ViewModel utilities for Compose
    def lifecycle_version = "2.6.2"
    implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"
}

新しいファイルを作成して、ViewModelを継承したクラスを作る。

package com.example.mycounter

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel

// MVVMを使用するには、ViewModelクラスを継承する必要があります。
class MyViewModel(): ViewModel(){
    // mutableStateOfは、値が変更されると、Composable関数が再描画されることを保証します。
    var count by mutableStateOf(0)
    // カウンターを増やす関数
    fun increaseCounter(){
        // countの値を1増やす
        count++

    }
}

MainActivity.ktにUIを作って、ViewModelを呼び出せば状態管理されたカウンターアプリを作れます。

package com.example.mycounter

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.mycounter.ui.theme.MyCounterTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyCounterTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    Counter()
                }
            }
        }
    }
}

// MyViewModelを引数に渡すことで、Counter関数内でMyViewModelを使用できるようになります。
@Composable
fun Counter(model: MyViewModel = viewModel()) {
    // Columnは、縦方向に要素を配置するためのComposable関数です。
    Column() {
        // Buttonは、ボタンを表示するためのComposable関数です。
        Button(onClick = {
            // model.increaseCounter()を呼び出すことで、カウンターを増やすことができます。
            model.increaseCounter()
        }) {
            // ボタンの名前のTextを表示します。
            Text(text = "Click Me")
        }
        // カウンターを表示するTextを表示します。
        Text(
            // model.countを呼び出すことで、カウンターの値を取得できます。
            text = "The Counter is: ${model.count}",
            // fontSizeを42.spに設定することで、文字の大きさを42spに設定します。
            fontSize = 42.sp
        )
    }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    MyCounterTheme {
        Counter()
    }
}

ボタンを押して、数字がカウントされたらカウンターアプリの完成です!

thoughts

使ってみた感想ですが、FlutterでいうところのProviderのような感じでしたね。これが昔からあるViewModelなんですね〜。
ライフサイクルが似ていることから、FlutterのMVVMはこれの影響を受けているんだな〜と驚いきました!

Jboy王国メディア

Discussion