ViewModel을 사용하는 이유는 간단하다. MainActivity에 데이터를 저장하는 대신 ViewModel에 데이터를 저장하는 가장 큰 이유는 화면 회전을 좀 더 효율적으로 지원하고, 프로세스 종료시 데이터를 유지하고 관리하기 위함이다. ViewModel을 사용하면 화면이 필요로 하는 모든 데이터를 한 곳에 모아 데이터를 포맷하고 최종 결과에 쉽게 액세스할 수 있다.

Step 1

먼저, viewmodel을 사용하기전 2개의 라이브러리를 추가해야한다. Android 앱 프로젝트에서는 Gradle 빌드 시스템을 사용한다. Gradle은 프로젝트를 빌드하고 필요한 라이브러리를 관리하는 도구 중 하나이고, 'dependencies' 블록은 프로젝트가 필요로 하는 외부 라이브러리를 지정하는 부분으로, 이 라이브러리들은 해당 프로젝트의 소스 코드에서 사용될 수 있다. 그래들 스크립트 파일에서 'dependencies' 블록을 수정하면, 프로젝트가 사용하는 라이브러리를 추가, 업데이트, 또는 제거할 수 있다. 

 

아래는 build.gradle.kts의 코드들이다.

plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
}

android {
    namespace = "edu.vt.mobiledev.multiquiz"
    compileSdk = 33

    defaultConfig {
        applicationId = "edu.vt.mobiledev.multiquiz"
        minSdk = 24
        targetSdk = 33
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = 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 { // BNRG Listing 2.6
        viewBinding = true
    }
}

dependencies {

    implementation("androidx.core:core-ktx:1.10.1")
    implementation("androidx.appcompat:appcompat:1.6.1")
    implementation("com.google.android.material:material:1.9.0")
    implementation("androidx.constraintlayout:constraintlayout:2.1.4")

    //ViewModel support
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1") // BNRG Listing 4.1
    implementation("androidx.activity:activity-ktx:1.7.2") // BNRG Listing 4.1


    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}

 

보면 여기 dependencies 블럭에 우리가 추가하고싶은 라이브러리를 추가해주면 된다(ViewModel support주석 아래 두줄). 앱이 컴파일 될때 gradle은 해당 dependencies들을 찾아내고, 알아서 다운로드 해서 포함시켜준다!(얼마나 간단한가? 수많은 개발자들이 갈려나갔을것이다,,,

 

***gradle파일을 수정한 후에는 "동 기 화" 하는것을 잊지 말자!!!***

Step2

ViewModel의 하위 클래스인 QuizViewModel을 만들자.

 

package edu.vt.mobiledev.multiquiz

import androidx.lifecycle.ViewModel
import android.util.Log


private const val TAG = "QuizViewModel"
class QuizViewModel : ViewModel(){
    init {
        Log.d(TAG, "ViewModel instance created")
    }
    override fun onCleared() {
        super.onCleared()
        Log.d(TAG, "ViewModel instance about to be destroyed")
    }
}

 

위의 태그나 로그출력 코드들은 전부 다 디버깅을 하기 위함이다.

 

이제 MainActivity로 가서 ViewModel들을 추가한다.

 

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    private val quizViewModel: QuizViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        setContentView(binding.root)

        Log.d(TAG, "Got a QuizViewModel: $quizViewModel")

        binding.trueButton.setOnClickListener { view: View ->
            checkAnswer(true)
        }
        ...
    }

 

 Kotlin에서 `by` 키워드는 프로퍼티를 구현하는 방법 중 하나인데 프로퍼티의 기능을 외부 코드 단위에 위임하는 방법이다. Kotlin에서 흔히 사용되는 프로퍼티 델리게이트 중 하나는 `lazy`이다. `lazy` 프로퍼티 델리게이트를 사용하면 프로퍼티가 액세스될 때까지 초기화를 지연시켜 자원을 절약할 수 있다.

또한, 여기서 소개된 `viewModels()` 프로퍼티 델리게이트도 비슷한 방식으로 작동한다. QuizViewModel은 액세스되지 않는 한 초기화되지 않는다. 로깅 메시지에서 참조함으로써 프로퍼티를 초기화하고 동시에 값을 로깅할 수 있다.

`viewModels()` 프로퍼티 델리게이트는 내부에서 여러 작업을 처리한다. Activity가  처음으로 QuizViewModel을 쿼리할 때, `viewModels()`는 새로운 QuizViewModel 인스턴스를 생성하고 반환한다. 구성 변경 후에 QuizViewModel을 다시 쿼리할 때는 처음에 생성된 인스턴스를 반환한다. 활동이 종료될 때(화면에서 앱을 닫을 때), ViewModel-Activity 쌍은 메모리에서 제거된다.

활동 내에서 QuizViewModel을 직접으로 인스턴스화하지 말아야 한다. 대신에 `viewModels()`을 쓰자. 직접 ViewModel을 인스턴스화하는 것이 동일하게 작동할 것처럼 보일 수 있지만, 활동의 구성이 변경된 후에 동일한 인스턴스가 반환되는 이점을 잃게 된다.

 

이렇게 복잡하게 ViewModel을 Acitivity 에서 구현하지 않고 따로 구현하는 이유는 간단하다. MainActivity의 간결함과 MainActivity는 화면에 display하는 역할만 수행 할 뿐, 복잡한 로직이나 세부적인 데이터 내용을 숨겨 유지보수를 위함이다.

+ Recent posts