본문 바로가기

Android/MVVM

안드로이드 뷰모델

  • 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