▩ 목 차 ▩
1. ViewModel
1-1. ViewModel 이란 ?
1-2. ViewModel을 사용하려는 이유 ?
1-3. ViewModel 사용법
1-3-1. gradle 추가
1-3-2. Layout(xml) 파일
1-3-3. ViewModel 파일
1-3-4. Activity 파일
1-4. ViewModel 주의할 점
2. ViewModel 느낀점
저번 시간에 LiveData를 배워보았다. LiveData를 보니까 ViewModel과 함께 쓰는것 같았다. 솔직히 아직까지는 왜 ViewModel과 함께 쓰는지 이유를 잘 모르겠다.. 그러니 공부하면서 익혀보도록 하겠다.
■ 1. ViewModel ■
ViewModel은 Android JetPack의 구성요소 중 하나로, 본래는 ViewModel이란 이름은 소프트웨어 개발 디자인 패턴 중 하나인 MVVM(Model - View - ViewModel) 디자인 패턴으로부터 파생되었다.
MVVM의 관점에서 부르는 ViewModel과 Android Jetpack에 포함된 ViewModel 클래스를 구분을 어떻게 할까 ?
==> 흔히 Android JetPack에 포함된 ViwModel을 Android Achitecture ViewModle의 약자인 AAC ViewModel이라고 부르기도 한다.
- MVVM 패턴의 ViewModel : View와 Model 사이의 매개체 역할을 하고 View에 보여지게 데이터를 가공하는 역할을 한다.
- AAC의 ViewModel : 앱의 Lifecycle을 고려하여 UI 관련 데이터를 저장하고 관리하는 역할을 한다. 또한 LiveData와 함께 사용된다.
구글에서는 MVVM 패턴을 사용하여 앱을 만들 것을 권장하고 있고, MVVM의 ViewModel을 구현할 때 AAC ViewModel을 사용해서 구현하는 것이 좋다.
==> MVVM의 ViewModel을 위해 AAC ViewModel이 사용되므로, 우리는 이번 시간에는 AAC ViewModel에 대해 자세히 알아본다!
■ 1-1. ViewModel 이란 ?
Activity와 fragement와 같은 UI 컨트롤러의 로직에서 데이터를 다루는 로직을 분리하기 위해 등장한 Android JetPack 라이브러리이다. 액티비티와 프래그먼트의 데이터를 관리하는데 사용되며, 어플리케이션 내 다른 클래스들과의 커뮤니케이션 하는데 사용된다.
그렇다면 왜 UI 컨트롤러의 로직에서 데이터를 다루는 로직을 왜 분리를 해야 할까?
- UI 컨트롤러의 목적 : 데이터를 표시해주거나, 사용자가 어떤 작업을 했을때 반응을 보여주거나, 권한 요청과 같은 OS커뮤니케이션을 처리하는 것이 UI컨트롤러의 목적이다.
- 데이터 손실 방지 : UI 컨트롤러에서는 생명주기에 따라 앱이 활동중에 제거될 때마다 데이터를 저장시키고, 다시 생성될 때마다 데이터들을 다시 불러와야한다.
즉, 데이터를 복원하기 위해 onSaveInstanceState() 메서드를 사용해 bundle로 데이터를 복원시키는 방법은 작은 용량의 데이터에만 적합하고 데이터가 커지게 되면 적합하지 않다. 또한 담을 수 있는 데이터의 형태가 제한되고, 시간이 오래 걸린다.
( 구체적인 예시로 스마트폰을 회전을 하게 되면 텍스트가 초기 값으로 돌아가는 것을 알 수 있는데, 생명주기에 관련된 문제 때문이다. 왜냐하면 화면 회전이 이루어지면 액티비티 혹은 프래그먼트가 Destroy 됐다가 다시 Create 되기 때문에 기존의 데이터가 날라가는 것이다. 이때 onSaveInstanceState()메서드를 이용해도 되지만 작은 용량의 데이터에만 적합하다는 문제가 있다는 것이다. 이것을 해결해주는것이 뭐?? 바로 ViewModel이란 것이다. )
즉, UI 컨트롤러(액티비티, 프래그먼트)에서 데이터를 관리를 하자니 생명 주기에 따라서 값이 사라지고.. onSaveInstanceState() 메서드로 해결하려고 하니 문제가 많은 것이다.
==> UI 컨트롤러가 데이터에 관여하지 않도록 따로 떼어낼 순 없을까 해서 나온 것이 바로 ViewModel이다.
■ 1-2. ViewModel을 사용하려는 이유 ?
위에서 말했듯이 UI 컨트롤러가 데이터에 관여하지 않도록 따로 떼어낼 순 없을까 해서 나온것이 ViewModel이다.
예제를 보며 ViewModel을 사용하려는 이유를 구체적으로 알아보자.
class MyViewModel : ViewModel() {
private val users: MutableLiveData<List<User>> by lazy {
MutableLiveData().also {
loadUsers()
}
}
fun getUsers(): LiveData<List<User>> {
return users
}
private fun loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
위의 코드는 ViewModel의 예시이다.
ViewModel을 상속받는 클래스를 만들어 데이터를 저장하고 관리하는 로직을 간단하게 구현한 것이다.
위 코드를 통해 생성된 ViewModel은 액티비티 혹은 프래그먼트와 다른 생명주기를 가지게 된다.
구체적인 예시로,
- 액티비티가 최초 생성될 떄 일반적으로 ViewModel을 인스턴스화 하여 생명주기를 같이 시작한다.(Fragment 또한 가능하다.)
- Configuration 변경(Ex:화면 회전)이 발생할 때 액티비티가 다시 시작 되는 것을 확인 할 수 있다. 하지만 ViewModel은 여전히 메모리 상에 남아있는다. 이는 Activity 내부에서 Configuration 변경과 무관하게 유지되는 NonConfigurationInstances 객체를 따로 관리하기 때문이다.
- Activity의 finish() 호출 등에 의해 액티비티가 생명주기가 종료됨에 따라 내부의 LifecycleEventObserver를 통해 ViewModel도 onCleared() 콜백 메서드를 호출하고 종료된다.
이렇게 생명주기가 다른 ViewModel을 분리하게 되면 다음과 같은 장점이 있다.
- 생명 주기에 영향을 받지 않고 데이터를 유지 할 수 있다.
- UI 컨트롤러(액티비티, 프레그먼트)와 데이터가 분리된다.
- 프래그먼트 간의 데이터 공유가 쉬워진다.
위와 같은 장점이 있기에 ViewModel을 사용한다.
■ 1-3. ViewModel 사용법
( 데이터 바인딩, LiveData에 대해 알고 있어야 이해가 됩니다. https://bj-turtle.tistory.com/101 , https://bj-turtle.tistory.com/102)
■ 1-3-1. gradle 추가
작업하고자 하는 프로젝트 gradle에 google()가 있어야 한다. 자동생성 경우가 대부분이지만, 안되는 경우도 있기 때문에 살펴봐야 한다.
allprojects {
repositories {
google() // 프로젝트 만들면 기본으로 생성되긴 함
}
}
■ 1-3-2. Layout(xml) 파일
데이터 바인딩을 적용시킨 파일이다. 데이터 바인딩을 공부하고 오지 않았다면 왜 제일 최상단 태그가 <layout>으로 되어 있고 그 안에 <data> 태그와 우리가 눈에 보이는 xml 레이아웃(리니어,콘센트레이트 등)이 들어가는지 이해가 되질 않을 것이다.
<?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">
<data>
<variable
name="user"
type="com.example.selfstudy_kotlin.UserViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView_height"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="키 증가"
android:onClick="@{()->user.increase()}"/>
</LinearLayout>
</layout>
■ 1-3-3. ViewModel 파일
ViewModel를 만들기 위해선 ViewModel 클래스로부터 상속을 받아야한다. ( ViewModel 혹은 AndroidViewModel을 상속 받을 수 있는데 보통은 ViewModel을 상속받는다. )
ViewModel은 보통 LiveData와 같이 쓰인다.
아래의 코드는 화면에 보여줄 height라는 데이터와 괸리하는 로직이 ViewModel 안에 있는것 정도만 확인하면 된다.
class UserViewModel(): ViewModel() {
private var _height = MutableLiveData<Int>()
val height: LiveData<Int>
get() = _height
init {
_height.value = 170
}
fun increase() {
_height.value = _height.value?.plus(1)
}
}
■ 1-3-4. Activity 파일
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var userViewModel: UserViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
userViewModel = ViewModelProvider(this).get(UserViewModel::class.java)
binding.user = userViewModel
/*
val nameObserver = Observer<Int> { it ->
binding.textViewHeight.text = it.toString()
}
userViewModel.height.observe(this, nameObserver)
*/
// 위에 주석 달은 걸 줄이면 이거임.
userViewModel.height.observe(this, Observer {
binding.textViewHeight.text = it.toString()
})
}
}
ViewModel 인스턴스는 ViewModelProvide를 이용하여 생성한다. 여기서 this를 넘겨주는데 owner를 의미한다.
==> 즉, ViewModelStore를 누가 소유하고 있느냐인데, 여기선 MainActivity가 소유하고 있다.
*ViewModelStore : ViewModel 객체가 HashMap 구조로 저장되는 곳, 즉, get() 안에 있는 'UserViewModel'은 객체를 찾아오기 위한 key값으로 쓰이는 것, 만약 Key에 해당하는 Value가 없으면 생성하고 갖온다. 그래서 처음 뷰 모델 객체를 처음 만드는데도 set 따위가 아니라 get을 쓰는 이유이다.
즉, 우리가 알 수 있는 사실은
- 하나의 액티비티를 소유자로 지정해 사용하면 같은 ViewModel을 공유할 수 있다. ==> 데이터 공유 가능
- 뷰 모델을 각각 다른 소유자(다른 액티비티)가 생성하면 이는 별개의 메모리 공간을 사용하는 다른 객체가 된다.
(binding 변수는 데이터 바인딩을 통해 생성된 인스턴스이며, nameObserver 변수는 obseve라는 것인데, 데이터의 변동 사항을 감지하는 것이다. )
■ 1-4. ViewModel 주의할 점
ViewModel은 Activity, Fragment, Context를 참조하면 안된다.
==> ViewModel은 액티비티나 프래그먼트보다 긴 생명주기를 가지고 있다. 이 점에 대해 확실하게 깨닫을 수 있는 예시를 들어보자면,
뷰 모델이 액티비티에 대한 참조를 가지고 있다고 했을때 가로회전, 세로회전을 100번 이상을 하게 했다고 가정했을때 액티비티는 종료와 생성을 반복하지만 ViewModel은 쭉 살아 있기 때문에 Memory Leak가 발생할 수 있다.
( applicationContext는 액티비티의 생명주기가 아닌 애플리케이션의 생명주기를 가지기 때문에 참조를 해도 괜찮다. 그래서 아까 말한 특별한 경우인 AndroidViewModel을 상속받는것이다. )
■ 2. 주의할 점 ■
■ 2-1. MVVM의 ViewModel과 AAC의 ViewModel은 같다?
나는 다른 블로그들을 참고하여 간단하게 예제를 만들어보면서, ViewModel에 관해 의문이 들었다.
찾아보니 MVVM 디자인패턴에서의 ViewModel이 있고, AAC의 ViewModel이 있던 것 이다.
( *AAC(Android Architecture Components) : 테스트와 유지보수가 쉬운 앱을 디자인할 수 있도록 돕는 라이브러리 모음이며, Lifecycles, LiveData, ViewModel, Room, Paging을 포함하여 총 5개의 라이브러리로 구성되어 있다. )
■ 2-1-1. MVVM에서의 ViewModel이란,
우선, MVVM 패턴의 목표에 대해 알아볼 필요가 있다. 목표는 비즈니스 로직과 프레젠테이션 로직을 UI로부터 분리하는 것이고 이렇게 하게 된다면 테스트, 유지 보수 측면에서 용이하다.
이러한 목표를 가지고 있는 MVVM 패턴에서의 ViewModel은 View에 연결될 데이터와 메서드를 구현하고, 상태가 변화하게되면 변경 알림 이벤트를 통해 View에게 상태 변화를 알려주고 View에서는 ViewModel을 알고 있지만, ViewModel을 알지 못한다.
일반적으로 ViewModel과 View는 1:n이기 때문에 View는 자신이 이용할 ViewModel을 선택하여 상태 변화 알림을 받게 되는데, ViewModel은 View가 쉽게 사용할 수 있도록 Model의 데이터를 가공하여 View에게 제공한다.
즉, 간단하게 MVVM에서의 ViewModel이란 View와 Model 사이에서 데이터를 관리하고 바인딩해주는 요소라고 생각하면 된다.
■ 2-1-2. AAC에서의 ViewModel이란,
이것도 마찬가지로 AAC를 사용하려는 목표에 대해 알아볼 필요가 있다. AAC의 목표는 '안드로이드는 여러 컴포넌트들로 이루져 있지만, 생명주기가 다르게 엮어있다. 앱을 잘 만들기 위해선 이러한 컴포넌트들을 부드럽게 연결해야하는데 그러기 위해선 생명주기를 학습하고 엉키지 않게 해야하는데 이것에 도움을 주는 라이브러리가 AAC 인 것이다.
이러한 생명주기에 관련되어 도움을 주는 목표를 가지고 있는 AAC의 ViewModel은 Android의 수명 주기를 고려하여 UI 관련 데이터를 저장하고 관리하도록 설계되었다.
AAC ViewModel을 사용하게 되면 기존의 Activity가 데이터 관리 측면에서 생명 주기 때문에 겪던 어려움들을 간단하게 처리할 수 있다.
즉, Activity가 생성되고 파괴되기 전까지 ViewModel은 파괴되지 않고 유지하게 된다. 이러한 AAC ViewModel을 사용하는 곳을 말해본다면, 화면 회전을 하는 액션에서 쓰이며 회전을 했을 시 데이터가 파괴되는 것을 보존할 수 있게 된다.
■ 2-1-3. MVVM에서의 ViewModel vs AAC에서의 ViewModel
위에서 말한 각각의 쓰임에 대해 간단하게 말해보자면,
- MVVM의 ViewModel : View에 필요한 데이터를 관리하여 바인딩 해주고, 비즈니스 로직을 담당해 처리하는 요소
- AAC의 ViewModel : Android의 수명주기를 고려하여 UI 관련 데이터를 저장하고 관리하는 요소
==> MVVM의 ViewModel 과 AAC의 ViewModel의 역할을 보게되면 확실하게 다른 개념인 것을 확인 할 수 있다.
그렇지만 두 개의 ViewModel은 아예 관련이 없는 것은 아니다. 왜냐하면 AAC의 ViewModel을 통해서 MVVM 패턴의 ViewModel을 구현 할 수 있기 때문이다.
이것을 바꿔 말하게 되면 Android의 수명주기를 고려하여 UI 관련 데이터를 저장하고 관리하는것(AAC의 ViewModel)을 통해서 View에 필요한 데이터를 관리하여 바인딩 해주고, 비즈니스 로직을 담당해 처리하는 요소의 역할(MVVM의 ViewModel)을 충분히 만들 수 있다는 것으로 해석할 수 있다.
즉, AAC의 ViewModel을 MVVM의 ViewModel처럼 사용하기 위해서는 ObservableField나 LiveData등을 사용하여 데이터 바인딩을 해준다면 MVVM 패턴의 ViewModel처럼 사용할 수 있을 것이다.
■ 3. ViewModel 느낀점 ■
ViewModel은 LiveData랑 거의 세트처럼 다닌다. 내가 생각한 이유는 LiveData 단독으로만 사용할 때, UI를 업데이트하는 경우 일반적으로 Activity나 Fragment 내에서 직접 선언하는 것보다 ViewModel 내에서 정의하고 호출하는 것이 더 좋기 때문이라고 생각한다. 왜냐하면 ViewModel을 사용하게 되면 Activity나 Fragment간의 결합도를 낮추고(의존성), UI Controller는 오직 data를 보여주는 역할을 하기 때문에 이상적인 디자인 패턴을 유지하기 때문이다.
'디자인패턴(MVC,MVP,MVVM)' 카테고리의 다른 글
Android MVVM 패턴 적용하기( feat. AAC[DataBinding, LiveData, ViewModel, RoomDB], Coroutine ) 적용해 메모장 만들기 (0) | 2022.12.14 |
---|---|
LiveData 란 ? (0) | 2022.11.17 |
데이터 바인딩(Data binding) (0) | 2022.11.16 |
뷰 바인딩(view binding) (0) | 2022.11.16 |
MVC vs MVP vs MVVM (0) | 2022.11.10 |