- ViewModel은 단순히 뷰 (액티비티 및 프래그먼트)에 대한 모델 클래스입니다.
- 데이터 트랜잭션을위한 메서드를 제공하므로 활성화 및 조각이 호출하여 작업을 완료 할 수 있습니다.
- 구성 변경이 발생하면 활동과 조각이 파괴되고 다시 생성됩니다. 따라서 지역적으로 보유한 가치는 손실 될 수 있습니다.
- Activities and fragments destroy and recreate when configuration changes happen . So values they hold locally, can be lost.
- 그러나 ViewModels는 그런 식으로 파괴하지 않습니다. 따라서 그들은 활성 및 조각에 속하는 값을 보유 할 수 있습니다.
- ViewModel의 onCleared ()는 시스템의 메모리를 확보하기 위해 앱이 백그라운드에 배치되고 앱 프로세스가 종료 될 때만 호출됩니다.
이 튜토리얼에서는 가능한 가장 간단한 프로젝트 예제를 사용하여 Android Jetpack ViewModel이 작동하는 방식을 보여 드리겠습니다. 먼저 뷰 모델을 사용하지 않고 만든 앱을 보여 드리겠습니다. 문제가 무엇인지 알 수 있습니다. 둘째, 해당 프로젝트에서 ViewModel을 사용하여 문제를 해결합니다. 마지막으로 뷰 모델 팩토리에 대해 배웁니다.
ViewModel 예제 없음
이 시작 프로젝트를 github에서 다운로드하고 Android Studio에서여십시오.
>>이 튜토리얼의 시작 프로젝트를 다운로드하기위한 GITHUB 링크
코드를 매우 빠르게 살펴 보겠습니다.
activity_main.xml에서 Button과 TextView를 추가하여 클릭 수를 표시했습니다.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/count_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="66sp"
android:textStyle="bold"
android:typeface="serif"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.262" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click Here"
android:textSize="30sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
MainActivity에서 클릭 카운트 값을 유지하는 변수를 정의했습니다.
private var count = 0
버튼의 클릭 리스너를 구현했습니다. 그리고 각 클릭에 대한 카운트 값을 증가시키는 코드를 작성하고 TextView에 카운트 값을 표시합니다.
binding.button.setOnClickListener {
count++
binding.countText.text = count.toString()
}
다음은 전체 MainActivity.kt 코드입니다.
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private var count = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.countText.text = count.toString()
binding.button.setOnClickListener {
count++
binding.countText.text = count.toString()
}
}
}
이제 에뮬레이터로 시작 프로젝트를 실행 해 보겠습니다.
그런 다음 "여기를 클릭하십시오"버튼을 클릭하십시오. 클릭 할 때마다 카운트 값이 하나씩 증가하는 것을 볼 수 있습니다.
그런 다음 에뮬레이터를 회전합니다. 카운트 값이 다시 "0"으로 변경되는 것을 볼 수 있습니다.
구성 변경이 발생하면 액티비티 및 프래그먼트는 어떻게됩니까?
- Android 앱을 사용할 때 화면 회전과 같은 구성 변경이 발생하면 앱이 새로운 구성으로 활동 또는 프래그먼트를 파괴하고 다시 만들어야합니다.
- 그 결과 활동 실행 기간 동안 생성 된 값도 소멸됩니다.
- 이것이 회전 후 처음부터 클릭 수 값이 시작된 이유입니다.
Android의 구성 변경은 무엇입니까?
- 화면 회전.
- 키보드 변경.
- 언어 변경.
- 다중 창 모드 활성화.
이 ViewModel 튜토리얼에서 사용한 앱은 매우 간단합니다. 그러나 이것은 더 큰 응용 프로그램과 관련하여 중요한 문제입니다.
예를 들어 원격 REST API에서 2000 개의 제품에 대한 데이터를 가져와야한다고 생각해보십시오. 구성 변경이 발생할 때마다 앱은 API를 호출하고 데이터를 반복해서 다운로드해야합니다.
그 결과 많은 데이터와 시스템 리소스가 불필요하게 낭비됩니다. 뿐만 아니라 사용자는 다운로드가 완료 될 때까지 몇 번이고 기다려야합니다. 결과적으로 앱은 매우 나쁜 사용자 경험을 제공합니다.
ViewModel comes to rescue.
- 위의 문제에 대한 해결책으로 Android Jetpack View Model 아키텍처 구성 요소가 도입되었습니다.
- 이름에서 알 수 있듯이 뷰 모델은 뷰의 모델입니다. UI 관련 데이터를 저장하고 관리하도록 설계되었습니다.
- 일반적으로 활동 또는 조각에 대해 하나의보기 모델을 만듭니다. 그러나 때로는 두 개 이상의 조각이 하나의 뷰 모델을 공유 할 수 있습니다.
- ViewModel의 onCleared ()는 앱이 백그라운드에 배치되고 시스템 메모리를 확보하기 위해 앱 프로세스가 종료 될 때만 호출됩니다.
- 따라서 활동 및 조각의 수명주기 변경은 해당 ViewModel에 영향을주지 않습니다.
그래서 우리는 우리 프로젝트에 뷰 모델을 사용할 것입니다.
ViewModel에 대한 Gradle 종속성.
앱 수준 build.gradle 파일을 열고이 종속성을 추가합니다. gradle을 동기화하십시오.
def lifecycle_version = "2.3.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
다음은 종속성을 추가 한 후의 전체 앱 수준 build.gradle 파일입니다.
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.anushka.viewmodeldemo_final1"
minSdkVersion 26
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
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{
dataBinding = true
}
}
dependencies {
def lifecycle_version = "2.3.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
ViewModel 클래스.
다음으로 ViewModel을 확장하여 뷰 모델 클래스를 생성합니다.
새 클래스를 만들고 MainActivityViewModel로 이름을 지정합니다 (원하는 이름을 지정할 수 있음). 그런 다음 ViewModel 클래스의 하위 클래스로 만듭니다. class MainActivityViewModel : ViewModel ( )
이 클래스 내에서 count 값을 보유 할 변수를 정의해야합니다. 개인 변수 수 = 0
그 후 클릭 할 때마다 카운트 값을 1 씩 증가시키는 함수 (방법)를 생성합니다. MainActivity에서이 함수를 호출합니다.
fun getUpdatedCount():Int{
return ++count
}
또한 카운트 값을 반환하는 함수를 만듭니다.
fun getCurrentCount():Int{
return count
}
여기 내 전체 MainActivityViewModel.kt 클래스가 있습니다.
class MainActivityViewModel : ViewModel() {
private var count = 0
fun getCurrentCount():Int{
return count
}
fun getUpdatedCount():Int{
return ++count
}
}
ViewModelProvider
이제 MainActivity.kt로 돌아가서 새로 생성 된 뷰 모델을 사용하기 위해 약간의 변경을하겠습니다.
클래스 맨 위에 ViewModel에 대한 개체 참조 변수를 정의하여 시작하겠습니다.
private lateinit var viewModel: MainActivityViewModel
다음으로 onCreate 내부에서 ViewModelProvider를 사용하여 인스턴스 (객체)를 생성하는 코드를 작성합니다.
자체적으로 ViewModel 인스턴스를 생성 할 수 없습니다. ViewModel의 인스턴스를 생성하려면 Android에서 제공하는 ViewModelProvider 유틸리티를 사용해야합니다.
viewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)
이제 MainActivityViewModel 클래스에서 정의한 메서드를 사용하여 카운트 값을 업데이트하고 가져올 수 있습니다.
binding.countText.text = viewModel.getCurrentCount().toString()
binding.button.setOnClickListener {
binding.countText.text = viewModel.getUpdatedCount().toString()
}
여기에 새로운 변경 사항을 적용한 MainActivity.kt가 있습니다.
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var viewModel: MainActivityViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
viewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)
binding.countText.text = viewModel.getCurrentCount().toString()
binding.button.setOnClickListener {
binding.countText.text = viewModel.getUpdatedCount().toString()
}
}
}
따라서 앱을 다시 실행하고 이러한 새 코드 변경이 의도 한대로 작동하는지 확인하겠습니다.
"여기를 클릭하십시오"버튼을 몇 번 다시 클릭하십시오. 클릭 할 때마다 카운트 값이 하나씩 증가하는 것을 볼 수 있습니다.
그런 다음 에뮬레이터를 회전합니다. 카운트 값이 다시 "0"으로 변경되지 않는 것을 볼 수 있습니다. 대신 버튼을 클릭하면 카운트 값이 다음 숫자로 증가합니다.
ViewModel Factory
Android에서 ViewModel 팩토리가 필요한 이유는 무엇입니까?
- 이 자습서의 앞부분에서 논의했듯이 자체적으로 ViewModel 인스턴스를 생성 할 수 없습니다.
- 따라서 Android에서 제공하는 ViewModelProvider 유틸리티를 사용하여 ViewModel의 인스턴스를 만들어야합니다.
- 그러나 ViewModelProvider는 (위에서 연구 한 것과 같은) arg 생성자가없는 ViewModels 만 인스턴스화 할 수 있습니다.
- 따라서 ViewModel에 생성자 parameters (arguments)가있는 경우 ViewModelProvider는 인스턴스를 생성하기 위해 약간의 추가 지원 이 필요 합니다.
- Factory 클래스를 만들고 해당 인스턴스를 ViewModelProvider에 전달하여 추가 지원을 제공합니다.
- 좋은 소식은 ViewModel Factory에 일반적인 상용구 코드가 있다는 것입니다. 따라서 클래스 이름과 매개 변수를 변경하여 동일한 코드를 모든 프로젝트에 쉽게 재사용 (복사 붙여 넣기) 할 수 있습니다.
이제 View Model 클래스에 생성자 매개 변수를 추가하여 시작하겠습니다.
시작 횟수가 있다고 가정 해 봅시다. 또한 활동에서 모델로 전달해야한다고 가정 해 보겠습니다. (나는 이것이 그다지 실용적이지 않다는 것을 알고 있지만 ViewModelFactory에 대한 가장 간단한 예제를 제공하려고합니다)
class MainActivityViewModel ( startingCount : Int ) : ViewModel ( ) {
다음으로 전달 된 "startingCount"int 값을 MainActivityViewModel의 "count"변수에 할당해야합니다.
private var count = startingCount
그래서 여기에 수정 된 MainActivityViewModel 팩토리 클래스가 있습니다.
class MainActivityViewModel(startingCount : Int) : ViewModel() {
private var count = startingCount
fun getCurrentCount():Int{
return count
}
fun getUpdatedCount():Int{
return ++count
}
}
ViewModel Factory Class
새 Kotlin 클래스를 만들고 MainActivityViewModelFactory로 이름을 지정합니다.
ViewModel 팩토리 클래스에 ViewModel 클래스 와 동일한 매개 변수 를 추가 해야 합니다 .
Also, this should extend ViewModelProvider.Factory
여기에 내 MainActivityViewModelFactory.kt가 있습니다.
class MainActivityViewModelFactory(private val startingCount : Int) : ViewModelProvider.Factory{
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainActivityViewModel::class.java)){
return MainActivityViewModel(startingCount) as T
}
throw IllegalArgumentException("Unknown View Model Class")
}
}
위의 코드는 일반적인 재사용 가능한 코드 부분입니다. 여러분이해야 할 일은 클래스 이름, ViewModel의 이름과 다른 프로젝트에 이것을 사용하기 위해 매개 변수 목록을 변경하는 것입니다.
이제 MainActivity.kt로 돌아갑니다. 뷰 모델 팩토리에 대한 객체 참조 변수를 정의해야합니다.
private lateinit var viewModelFactory : MainActivityViewModelFactory
그런 다음 onCreate 내부에서 startingCount에 대한 값 (원하는 숫자)을 전달하여 인스턴스화합니다.
viewModelFactory = MainActivityViewModelFactory ( 125 )
마지막으로 해당 viewModelFactory 인스턴스를 ViewModelProvider에 전달해야합니다.
viewModel = ViewModelProvider(this,viewModelFactory).get(MainActivityViewModel::class.java)
그래서, 이것이 새로운 Factory 관련 변경 후 MainActiivty.kt의 모습입니다.
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var viewModel: MainActivityViewModel
private lateinit var viewModelFactory: MainActivityViewModelFactory
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
viewModelFactory = MainActivityViewModelFactory(125)
viewModel = ViewModelProvider(this,viewModelFactory).get(MainActivityViewModel::class.java)
binding.countText.text = viewModel.getCurrentCount().toString()
binding.button.setOnClickListener {
binding.countText.text = viewModel.getUpdatedCount().toString()
}
}
}
이제 앱을 실행하면 추가 한 시작 값을 보여주는 TextView를 볼 수 있습니다. 버튼을 클릭하면 해당 값이 하나씩 증가합니다.
AndroidViewModel vs ViewModel
AndroidViewModel 클래스는 ViewModel 클래스의 하위 클래스입니다. 애플리케이션 컨텍스트 인식 ViewModel로 설명됩니다.
Viewmodel 내에서 컨텍스트를 사용해야 할 때 AndroidViewModel을 사용해야합니다. 응용 프로그램 컨텍스트를 포함하기 때문입니다.
예를 들어, ViewModel 대신 AndroidViewModel을 확장 한 경우 MainActivityViewModel 클래스가 어떻게 생겼는지 보여줍니다.
class MainActivityViewModel(startingCount : Int, app:Application) : AndroidViewModel(app) {
private var count = startingCount
fun getCurrentCount():Int{
return count
}
fun getUpdatedCount():Int{
return ++count
}
}
이제 또 다른 생성자 매개 변수
app:Application
그리고이를 AndroidViewModel의 constructor (super class constructor) AndroidViewModel ( app )에 전달했습니다.
언제 ViewModel 내부에 애플리케이션 컨텍스트가 필요합니까?
- 데이터베이스를 시작합니다.
- 시스템 서비스 이용시 (인터넷 사용 가능 여부 확인,…)
- 공유 기본 설정을 사용합니다.
Android에서는 특정 클래스에 Application 인스턴스가있는 것이 일반적으로 문제가되지 않습니다. 그러나 다른 Application 인스턴스가 해당을 참조하는 경우 참조주기 오류가있을 수 있습니다.
원문
https://appdevnotes.com/android-viewmodel-tutorial-for-beginners-in-kotlin/
'Android > MVVM' 카테고리의 다른 글
Two Way Data Binding vs One Way Data Binding (0) | 2021.05.20 |
---|---|
Live Data (0) | 2021.05.20 |
안드로이드 데이터 바인딩 (0) | 2021.05.20 |
02. MVVM 적용 샘플 #01 (0) | 2021.03.31 |
#01. MVVM (0) | 2021.03.31 |