SW 공부노트

[안드로이드/Kotlin] ViewModel 본문

안드로이드/안드로이드 공부

[안드로이드/Kotlin] ViewModel

요빈 2023. 4. 12. 16:10

ViewModel

ViewModel은 뷰에 표시되는 앱 데이터 모델으로 UI 관련 데이터를 저장하고 관리해주는 역할을 한다.

생명주기에 영향을 많이 받는 UI 컨트롤러가 데이터에 관여하지 않도록 따로 분리한 것이다.

 

ViewModel은 Android 프레임워크에서 View가 소멸되고 다시 생성되어도 데이터를 계속해서 유지한다는 큰 장점이 있다.

따라서 구성이 변경되는 동안 데이터가 유지되기 때문에, 다시 생성된 뷰에서 데이터를 즉시 사용할 수 있다.

 

ViewModel은 액티비티 혹은 프래그먼트와 다른 생명주기를 가진다.

finish 메서드가 호출되거나, 사용자가 직접 액티비티를 종료했을 때 onCleared 메서드를 통해 소멸된다.

ViewModel 객체의 범위는 ViewModel을 가져올 때 ViewModelProvider에 전달되는 Lifecycle로 지정된다.

ViewModel은 범위가 지정된 Lifecycle이 영구적으로 종료될 때까지, 즉, 액티비티에서는 액티비티 끝날때까지,  프래그먼트에서는 프래그먼트가 분리될 때까지 메모리에 남아있는다.

 

ViewModel의 생명주기가  UI 컨트롤러(A / F)의 생명주기보다 길기 때문에 UI 컨트롤러의 context를 참조하면 안 된다.

예를 들어, 화면 rotation이 발생하면 액티비티는 destroy되고 다시 생기는데 ViewModel은 이 경우에도 액티비티가 완전히 종료될 때까지 유지된다. 그러므로 ViewModel이 액티비티의 context를 받아서 계속 사용한다면, 액티비티가 이미 destroy 되었을 때 뷰 모델은 이미 종료되어 사라진 액티비티의 context를 가지고 있을 것이다. 즉, 쓸데없는 것이 메모리를 차지하고 있는 현상인 Memory Leak이 발생하게 되는 것이다.  

 

이렇게 뷰 모델을 분리하면 다음과 같은 장점이 있다.

 

  • 생명주기에 영향을 받지 않고 데이터를 유지할 수 있다.
  • UI 컨트롤러와 데이터가 분리된다.
  • 프래그먼트 간의 데이터 공유가 쉬워진다.

 

* ViewModel을 확장하여 Application을 참조하고 있는 AndroidViewModel이라는 것이 있는데, 이것을 통해 applicationContext를 사용하면 앞서 언급한 문제 없이 Context를 사용할 수 있다.


Gradle 설정

implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'

// ViewModel 속성위임 초기화에 사용
implementation 'androidx.activity:activity-ktx:1.7.0'
implementation 'androidx.fragment:fragment-ktx:1.5.6'

ViewModel 클래스 인스턴스 생성

ViewModel 클래스를 상속해 정의한 클래스는 개발자가 생성자를 통해 인스턴스를 생성할 수  없고,  ViewModelProvider.Factory 인터페이스를 통해야만 인스턴스화할 수 있다.

 

ViewModel은 ViewModelStore라는 객체에서 관리를 한다.

ViewModelStore는 ViewModel 객체가 HashMap 구조로 저장되는 곳이다.

(그렇기 때문에 ViewModel 객체 생성 시 get을 쓰는 것)

 

ViewModelStore 객체는 ViewModelStoreOwner가 생성하고 관리한다.

ViewModelStoreOwner 인터페이스를 FragmentActivity 의 부모격인 ComponentActivity와 Fragment 클래스가 구현(Implement) 하고 있기 때문에 ViewModel 객체를 생성할 때마다 Activity나 Fragment가 필요하고, 어떤 Owner를 통해 생성하느냐에 따라 ViewModel의 Scope이 정해진다.

 

이를 통해 두 가지 사실을 알 수 있다.

 

  • 뷰 모델을 각각 다른 소유자가 생성하면, 이는 별개의 메모리 공간을 사용하는 다른 객체가 된다.
  • 하나의 액티비티를 소유자로 지정해 사용하면 같은 ViewModel을 공유할 수 있다 -> 데이터 공유 가능

* ViewModel -> ViewModelProvider.Factory(인터페이스)를 통해 생성

ViewModel -> ViewModelStore가 관리 -> ViewModelStoreOwner(인터페이스)

 

즉, ViewModel을 생성하려면 ViewModelProvider.Factory 구현체(커스텀, 안드로이드 디폴트 etc)와 ViewModelStoreOwner 구현체(Activity, Fragment)가 필요하다.

 

ViewModel 인스턴스를 생성하는 방법은 크게 3가지로 나눌 수 있다.

 

  • 속성 위임 사용
  • 기본 ViewModelProvider.Factory
  • 커스텀 ViewModelProvider.Factory

1. 속성 위임 통해 ViewModel 내에서만 사용할 변수와 외부에서 사용할 변수 선언

private val viewModel:GameViewModel by viewModels()

속성 위임을 이용한 초기화는 해당 viewModel이 초기화되는 Activity 혹은 Fragment의 Lifecycle에 종속된다.

* by activityViewModels()는 프래그먼트에서만 사용 가능한 viewModel 초기화 방식이다.

프래그먼트는 액티비티에 종속되어 있기 때문에 위 속성 위임을 사용하면 여러 프래그먼트에서 하나의 ViewModel을 공유하여 사용할 수 있다.

 

2. 파라미터 없는 ViewModel

 

ViewModelProvider.NewInscatnceFactory: ViewModelProvider.Factory 인터페이스 구현(안드로이드 기본 제공)

noParamViewModel = ViewModelProvider(this).get(NoParamViewModel::class.java)

noParamViewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())
            .get(NoParamViewModel::class.java)

 

3. 파라미터 있는 ViewModel: ViewModelProvider.Factory 커스텀(아래 추가 설명)

 

  • ViewModelProvider.Factory 커스텀: 하나의 팩토리로 다양한 ViewModel 클래스를 관리할 수 있다.

 

- ViewModelProvider.Factory 인터페이스 커스텀 클래스 생성

 

ViewModel 객체를 생성하는 ViewModelProvider.Factory 인터페이스를 상속하는 클래스를 만들어야 한다.

조건문에서 다른 ViewModel 클래스로 분기하면 한 팩토리에서 여러 뷰모델을 생성할 수 있다.

이 부분에서 팩토리 패턴이라는 디자인 패턴을 사용한 것이다.

 

* ViewModel 클래스 내에서 companion object로 선언될 수도 있음

class NoParamViewModelFactory : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return if (modelClass.isAssignableFrom(NoParamViewModel::class.java)) {
            NoParamViewModel() as T
        } else {
            throw IllegalArgumentException()
        }
    }
}

// ViewModel 클래스 객체 생성 코드
viewModel = ViewModelProvider(this, NoParamViewModelFactory()).get(NoParamViewModel::class.java)

// 속성 위임
private val viewModel: NoParamViewModel by viewModels { NoParamViewModelFactory() }

파라미터가 있는 ViewModel도 ViewModelProvider.Factory인터페이스를 상속한 커스텀 팩토리를 통해 생성할 수 있다.

class HasParamViewModelFactory(private val param: String) : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return if (modelClass.isAssignableFrom(HasParamViewModel::class.java)) {
            HasParamViewModel(param) as T
        } else {
            throw IllegalArgumentException()
        }
    }
}

// ViewModel 클래스 객체 생성 코드
viewModel = ViewModelProvider(this, HasParamViewModelFactory("string")).get(HasParamViewModel::class.java)

프래그먼트 간 데이터 공유

액티비티에 포함된 둘 이상의 프래그먼트는 서로 공통된 데이터를 다루는 것이 일반적이다.

둘 이상의 프래그먼트가 공통된 데이터를 다루기 위해 공유 ViewModel을 사용한다.

 

by activityViewModels() 속성 위임을 통해 공유 ViewModel 인스턴스를 생성한다.

class ListFragment : Fragment() {

	private val model: SharedViewModel by activityViewModels()
	...
}

class DetailFragment : Fragment() {

	private val model: SharedViewModel by activityViewModels()
	...
}

 

두 프래그먼트는 모두 자신이 포함된 액티비티를 가져온다.

그렇게 되면 각 프래그먼트는 ViewModelProvider를 가져올 때 이 액티비티로 범위가 지정된 ViewModel 인스턴스를 받는다. 공유 ViewModel의 장점은 다음과 같다.

 

  • 액티비티는 자신이 포함한 여러 프래그먼트 간의 커뮤니케이션에 관해 어떤 것도 알 필요가 없다.
  • 프래그먼트 중 하나가 사라져도 다른 프래그먼트는 계속 작동한다. 
  • 각 프래그먼트는 자체 수명주기가 있으며, 다른 프래그먼트 수명 주기의 영향을 받지 않는다.

ViewModel로 로더 대체하기

로더 클래스는 앱의 UI 데이터와 데이터베이스 간의 동기화를 유지할 때 자주 사용된다.

ViewModel을 몇 가지 클래스와 함께 사용하여 로더를 대체할 수 있다.

ViewModel을 사용하면 UI 컨트롤러가 데이터 로드 작업에서 분리되 클래스 간의 의존성이 줄어든다.

 

ViewModel은 Room 및 LiveData를 통해 로더를 대체할 수 있다.

ViewModel은 기기 설정이 변경되어도 데이터가 유지되도록 보장한다.

데이터베이스가 변경되면 Room에서 LiveData에 변경을 알리고, 알림을 받은 LiveData는 수정된 데이터로 UI를 업데이트한다.

 

 


Reference

https://developer.android.com/topic/libraries/architecture/viewmodel?hl=ko 

 

ViewModel 개요  |  Android 개발자  |  Android Developers

ViewModel을 사용하면 수명 주기를 인식하는 방식으로 UI 데이터를 관리할 수 있습니다.

developer.android.com

https://readystory.tistory.com/176

 

[Android] AAC ViewModel 을 생성하는 6가지 방법 - ViewModelProvider

이 글은 이전 포스팅([Android] 화면 회전해도 데이터 유지하기 - AAC ViewModel)에 이어지는 글입니다. ViewModel 클래스를 상속하여 정의한 클래스는 개발자가 직접 생성자를 통하여서 인스턴스를 생성

readystory.tistory.com

https://dev-repository.tistory.com/116

 

Android ViewModel에서 Context를 올바르게 사용하는 방법

MVVM 구조로 안드로이드 개발을 진행하다 보면, ViewModel이나 Model에서 Context가 필요한 경우가 있다. 하지만, ViewModel의 Lifecycle이 Activity나 Fragment의 Lifecycle보다 길기 때문에 Activity/Fragment의 context를 V

dev-repository.tistory.com

👍 https://todaycode.tistory.com/33

 

 

안드로이드 View Model(뷰 모델)을 공부해보자!

1. ViewModel 1-1. ViewModel 이란? 1-2. 탄생 배경 1-3. 사용하는 이유 2. 사용법 2-1. gradle 추가 2-2. Layout 파일 2-3. ViewModel 파일 2-4. Activity 파일 3. 주의할 점 3-1. 참조 1. ViewModel 1-1. ViewModel 이란? Clean Architectur

todaycode.tistory.com