안녕하세요! 오늘은 최근 안드로이드 개발 패턴 중에 자주 쓰이고 있는 MVVM 패턴에서 VM을 담당하는 ViewModel(뷰모델)에 대해 간단히 알아보도록 하겠습니다.
뷰모델의 탄생 배경🤔
안드로이드 공식 개발문서에서는 ViewModel에 탄생 배경에 대해 아래와 같이 설명하고 있습니다.
Android 프레임워크는 활동과 프래그먼트 같은 UI 컨트롤러의 수명 주기를 관리합니다. 프레임워크는 특정 사용자 작업이나 완전히 통제할 수 없는 기기 이벤트에 대한 응답으로 UI 컨트롤러를 제거하거나 다시 만들도록 결정할 수 있습니다. 시스템에서 UI 컨트롤러를 제거하거나 다시 만드는 경우, 컨트롤러에 저장된 모든 일시적인 UI 관련 데이터가 삭제됩니다. 예를 들면 앱 활동 중 하나에 사용자 목록이 포함되어 있는데, 구성이 변경되어 활동이 다시 생성되면 새 활동이 사용자 목록을 다시 가져와야 합니다. 데이터가 단순한 경우 활동은 onSaveInstanceState() 메서드를 사용하여 onCreate()의 번들에서 데이터를 복원할 수 있습니다. 하지만 이 접근 방법은 사용자 목록이나 비트맵과 같은 대용량일 가능성이 높은 데이터가 아니라, 직렬화했다가 다시 역직렬화할 수 있는 소량의 데이터에만 적합합니다.
다른 문제는 UI 컨트롤러가 반환하는 데 시간이 걸릴 수 있는 비동기 호출을 자주 해야 한다는 점입니다. UI 컨트롤러는 비동기 호출을 관리해야 하며, 메모리 누수 가능성을 방지하기 위해 호출이 제거된 후 시스템에서 호출을 정리하는지 확인해야 합니다. 관리에는 많은 유지관리가 필요하며, 구성 변경 시 개체가 다시 생성되는 경우 개체가 이미 수행된 호출을 다시 호출해야 할 수 있으므로 리소스가 낭비됩니다.
활동 및 프래그먼트와 같은 UI 컨트롤러의 목적은 기본적으로 UI 데이터를 표시하거나, 사용자 작업에 반응하거나, 권한 요청과 같은 운영체제 커뮤니케이션을 처리하는 것입니다. 또한 UI 컨트롤러에 데이터베이스나 네트워크에서 데이터 로드를 책임지도록 요구하면 클래스가 팽창됩니다. UI 컨트롤러에 과도한 책임을 할당하면 다른 클래스로 작업이 위임되지 않고, 단일 클래스가 혼자서 앱의 작업을 모두 처리하려고 할 수 있습니다. 또한 이런 방법으로 UI 컨트롤러에 과도한 책임을 할당하면 테스트가 훨씬 더 어려워집니다.
"UI 컨트롤러 로직에서 뷰 데이터 소유권을 분리하는 방법이 훨씬 쉽고 효율적입니다."
간단하게 정리해보면, 액티비티나 프래그먼트와 같은 UI 컨트롤러에서 데이터를 관리하기엔 생명주기에 따라 값이 바뀌며, 그 해결방안인 onSaveInstanceState() 또한 한계가 존재하고 또, DB나 네트워크에서 데이터를 주고받을 때 클래스 과부하도 발생하기 때문에 'UI 컨트롤러가 데이터를 관여하지 않도록 따로 분리하기 위해 생겨난 방법' 이것이 바로' ViewModel(뷰모델)' 입니다!
뷰모델의 사용 이유👍
공식 개발문서에서 말한 UI 컨트롤러의 말한 문제점을 모두 보안할 수 있는 대안으로 나온 것이 바로 뷰모델입니다.
뷰모델은 액티비티나 프래그먼트와는 다른 생명주기를 가집니다.
또한 finish() 호출 및 사용자가 뒤로가기를 통해 액티비티를 종료했을 때, onCleared()를 통해 뷰모델은 소멸하는데, 이는 곧 액티비티나 프래그먼트에 비해 긴 수명주기를 가진다는 뜻입니다.
이렇듯 액티비티와는 다르고 긴 뷰모델의 수명주기를 통해 아래와 같은 장점을 얻을 수 있습니다.
- 생명주기에 영향을 받지 않고 데이터를 유지
- UI 컨트롤러와 데이터를 분리
- 프래그먼트 간의 데이터 공유가 용이

뷰모델 사용법📝
1. Gradle
// ViewModel
def lifecycle_version = "2.5.0-alpha06"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" // viewModel
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" // liveData
implementation 'androidx.activity:activity-ktx:1.4.0' // by viewModels()
implementation 'androidx.fragment:fragment-ktx:1.4.1' // by activityViewModels()
* LiveData는 뷰모델과 짝꿍처럼 붙어다니는 친구로, 옵저빙(Observing)을 통해 자동으로 데이터 변화를 감지!
2. 클래스 정의
/**
* ViewModel
*
* <주의사항>
* 뷰모델은 반드시 Activity, Fragment, Context 를
* 참조해선 안된다!
* 뷰모델은 위의 것들과 상이한 생명주기를 가지므로, 만약
* 위의 것들을 참조하면 이미 죽은 객체들을 계속 가지게됨으로
* '메모리 누수' 발생!!
*
* (단, applicationContext 는 애플리케이션 자체 생명주기를 가지므로
* 참조해도 괜찮다!
* = 이경우 ViewModel 이 아닌 AndroidViewModel 를 상속받는다.)
*/
class MainViewModel: ViewModel() {
private var _data = MutableLiveData<Int>()
val data: LiveData<Int>
get() = _data
init {
_data.value = 100
}
fun increase() {
_data.value = _data.value?.plus(1)
}
fun decrease() {
_data.value = _data.value?.minus(1)
}
}
ViewModel 클래스를 상속받아 원하는 뷰모델 클래스를 만들 수 있습니다.
뷰모델 사용시 주의사항이 있는데, 뷰모델에서는 context, activity, fragment를 참조해선 안됩니다..!!😮
그 이유는 이들이 뷰모델과 다른 생명주기를 가지기 때문입니다.
앞서 위에서 말했듯이 뷰모델은 액티비티나 프래그먼트와 같은 UI 컨트롤러와 다른 생명주기를 가지기 때문에, 이들을 참조하게되면 메모리 누수(Memory Leak)가 발생할 수 있는 문제가 있습니다.
만약 데이터베이스 접근과 같이 어쩔 수 없이 context를 참조해야 한다면 어떡해야 할까요?🤔
👉applicationContext의 경우 액티비티가 아닌 애플리케이션 생명주기를 가지기 때문에 사용이 가능한데 이때 AndroidViewModel를 이용하면 참조하여 뷰모델을 사용하실 수 있습니다.
보통 뷰모델 내 데이터 정의는 LiveData를 이용해서 하는데, 뷰모델의 데이터를 LiveData로 정의하게되면 뷰에서는 LiveData를 Observing하여 자동으로 데이터 변동을 감지해 그에 대한 UI만 변경시켜주면 됩니다.
UI 컨트롤러에서는 LiveData가 감지한 데이터를 이용해 UI 변경만 해주면되고, 뷰모델은 뷰(UI)에는 일체관여하지 않은채로, 데이터만 바라보고 데이터를 변경해주면 됩니다!!
3. 뷰에서의 뷰모델 사용
class MainActivity: BaseActivity() {
private lateinit var binding: ActivityMainBinding
private val vm: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
// vm = ViewModelProvider(this)[MyViewModel::class.java]
binding.viewModel = vm
binding.lifecycleOwner = this
/*
val observer = Observer<Int> {
binding.dataTv.text = it.toString()
}
vm.data.observe(this, observer)
*/
vm.data.observe(this) {
binding.dataTv.text = it.toString()
}
setListener()
}
fun setListener() {
binding.increaseBtn.setOnClickListener { vm.increase() }
binding.decreaseBtn.setOnClickListener { vm.decrease() }
}
}
뷰에서 뷰모델을 사용하기 위해 뷰모델을 먼저 정의해야합니다. 뷰모델 정의방법으로 2가지가 있습니다.
- ViewModelProvider 사용
- by viewModels() 사용
ViewModelProvider를 사용할 경우 ViewModelStore의 소유자(owner)를 지정해야합니다.
(위 코드에선 액티비티(this)가 되겠습니다.)
이후 ViewModelStore에서 사용할 뷰모델을 찾기 위해 get()을 하는데,
이는 ViweModelStore의 구조가 Map구조 되어 있기 때문입니다!
(MainViewModel::class.java는 필요한 뷰모델을 찾기위한 키값이라고 보시면 됩니다!)
두번째 방법은 공식문서에서 권장하는 방법으로 by viewModels()를 사용해 변수 선언부터 정의가 가능합니다!
(만약 import가 되지않는다면 위의 Gradle부분 주석을 참고해주세요!)
또다른 특징으로 LiveData를 Observing하여 UI를 변경하는 모습을 확인하실 수 있는데 식으로 표현하면 아래와 같습니다.
👉뷰모델.라이브데이터.observe(owner) { UI 변경 }
글을 마무리하며..✍
오늘은 뷰모델에 대해 간단하게 알아보았습니다.
요즘 대부분이 MVVM 패턴을 이용해 개발하기 때문에 최근들어 뷰모델에 대해 공부하기 시작했습니다.
기존 MVC패턴에 익숙했더지라 사용에 있어 다소 어색함이 있었지만, 뷰모델을 통해 코드가 깨끗해지는 느낌을 받았습니다.
이는 MVVM 패턴의 장점으로, 뷰는 UI에 관련된 코드만 관리하게되고, 뷰모델에서는 데이터에 관련된 코드만 관리하기 때문인데 이를 통해 유지보수 역시 타 패턴에 비해 간편함을 느낄 수 있었습니다.
이외에도 추후에는 Repository, Flow, Paging등 MVVM 패턴과 관련된 여러 기술들을 공부해야겠다고 느꼈습니다.
이런 최신기술을 지금이라고 공부할 수 있어서 다행이었고, 한편으론 안드로이드 공부에 있어서 한없이 부족함을 느껴 제자신이 조금은 부끄럽다?고 느껴지기도 했습니다..ㅎ
앞으로 더 열심히 공부해서 더 나은 주니어 안드로이드 개발자가 될 수 있도록 노력하겠습니다🙂
지금까지 부족한 짧은글 봐주셔서 감사합니다🙇
참고
https://developer.android.com/topic/libraries/architecture/viewmodel?hl=ko
ViewModel 개요 | Android 개발자 | Android Developers
ViewModel을 사용하면 수명 주기를 인식하는 방식으로 UI 데이터를 관리할 수 있습니다.
developer.android.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. View..
todaycode.tistory.com
https://onlyfor-me-blog.tistory.com/381
[Android] 뷰모델이란? + 뷰모델 코틀린 예제
MVVM 패턴을 구현하면 필수적으로 뷰모델이란 걸 구현해서 사용해야 한다. 그런데 이 뷰모델은 무슨 역할을 하는 것인가? 안드로이드 디벨로퍼에선 이렇게 말한다. https://developer.android.com/topic/libr
onlyfor-me-blog.tistory.com
'Android' 카테고리의 다른 글
| [안드로이드 / Kotlin] Dagger Hilt (0) | 2022.05.17 |
|---|---|
| [안드로이드 / Kotlin] Room (0) | 2022.04.27 |
| [안드로이드 / Kotlin] 키해시(Key Hash) 얻기 (0) | 2022.04.06 |
| [안드로이드 / Kotlin] 코루틴(Coroutine) (0) | 2022.03.21 |
| [안드로이드 / Kotlin] 타이머(Timer) 구현하기 (0) | 2022.03.18 |