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하는 역할만 수행 할 뿐, 복잡한 로직이나 세부적인 데이터 내용을 숨겨 유지보수를 위함이다.
'Android' 카테고리의 다른 글
[Kotlin] 안드로이드 앱 메뉴바 아이콘 추가하기[Menu] (0) | 2024.03.27 |
---|---|
[Kotlin] Jetpack Navigation 라이브러리 사용법 (0) | 2024.03.13 |
[Kotlin]viewBinding을 통한 레이아웃 (0) | 2024.01.24 |
[Kotlin]Android Studio를 이용한 간단한 퀴즈 앱 만들기 pt.2 (0) | 2024.01.20 |
[Kotlin]Android Studio를 이용한 간단한 퀴즈 앱 만들기 pt.1 (1) | 2024.01.19 |