! 안드로이드 권장 아키텍처 공부를 하고 나서 바로 차로 코드에 MVVM 패턴을 적용하려고 했는데, data binding부터해서 ViewModel에 분리하는 방법까지...너무 막막했다. 그래서 바로 코드를 리팩토링하는 것 보다는 하나하나씩 이해를 하려고 해서 공부한 뒤 정리를 해보려 한다.
Android developers - ViewModel
원래 ViewModel은 MVVM(Model, View, ViewModel)이라는 디자인 패턴의 구성요소로 부터 나왔다.
여기서의 ViewModel은 Android JetPack에 포함된 ViewModel로 MVVM패턴의 ViewModel과 구분 짓기 위해 AACViewModel이라고도 불린다.
우리가 ViewModel을 사용하는 이유는 유지보수와 개발 효율을 높이기 위해서이다. 이를 위해 ViewModel이라는 UI와 데이터 로직을 분리하는 방법을 사용하는 것이다.
예를 들어, Activity와 Fragment같은 UI 컨트롤러에 데이터베이스나 네트워크에서의 데이터 로드를 책임지도록 요구할 경우 클래스가 팽창하게 된다.(클래스 내에서 작성되는 코드가 증가하게 되겠지?)
UI 컨트롤러에 위와 같이 과도한 책임을 할당할 경우 다른 클래스로 작업이 위임되지 않고, 단일 클래스(UI 컨트롤러)가 혼자서 앱의 작업을 모두 처리하려 한다.
그러므로 UI 컨트롤러 로직에서 뷰와 관련한 데이터를 분리하는 ViewModel을 사용해야 한다.
추가로 수명주기 변동에 따라 데이터가 유지되지 않는 문제도 있는데 이는 Android 프레임워크에 대해 잠시 이해하고 갈 필요가 있다.
Android 프레임워크는 Activity/Fragment와 같은 UI 컨트롤러의 수명 주기를 관리한다. 프레임워크는 특정 사용자 작업이나 통제할 수 없는 기기 이벤트에 대한 응답으로 UI 컨트롤러를 제거하거나 다시 만들도록 결정이 가능하다.
이때 시스템에서 UI 컨트롤러를 제거하거나 다시 만드는 경우, 앞서 말했듯이 컨트롤러에 저장된 모든 일시적인 UI 데이터가 삭제된다.
데이터가 단순한 경우에는 onSaveInstanceState() 메소드를 사용하여 onCreate() 번들에서 데이터를 복원할 수 있지만, 대용량 데이터에는 적합하지 않다.
위와 같은 문제를 예로 들자면 앱의 화면을 가로 및 세로로 바꿀 때 발생한다.
Activity가 가로로 바뀔 때 Activity의 수명 주기가 onPause에서 onDestroy로 바뀌고 다시 onCreate에서 onResume으로 바뀐다.
이와 같은 수명 주기가 변동되는 상황은 UI 컨트롤러를 제거한뒤 다시 만드는 경우이기 때문에 지금까지 사용자가 가로 모드로 바꾸기 전까지 저장되었던 앱 데이터 유지되지 않고 다시 초기화 된다.
이 문제를 해결하는 여러가지 방법(onSaveInstanceState(), RetainedFragment등)이 있지만 각각의 방법에는 단점이 있다.
문제를 근본적으로 해결하는 방법이 바로 ViewModel이다.
ViewModel은 앞서 말했듯이 UI 컨트롤러에서 사용되는 UI 관련 데이터를 보관하고, 관리하기 위해 디자인되었다. 게다가 ViewModel의 인스턴스는 Activity의 onCreate()에서 요청을 하고, 아래 그림과 같이 Activity의 onCreate()가 여러 번 호출되는 상황에서도 ViewModel의 수명주기는 일관되게 유지됨을 볼 수 있다.
즉 한 Activity의 인스턴스가 소멸되고 다른 Activity의 인스턴스가 생성되더라도 계속해서 데이터를 유지할 수 있다.
그리고 ViewModel의 인스턴스가 더 이상 사용되지 않고 메모리에서 소멸되는 순간 onCleared()메소드가 호출된다.
연습을 위한 코드작성을 위해 개발하는 정대리님의 유튜브 영상을 보고 작성했다.
위의 그림과 같이 숫자 더하기 빼기 기능이 있는 앱이다.
dependencies {
def lifecycle_version = "2.4.0-alpha02"
//뷰모델 - 라이프 사이클 관련
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version")
// 라이브 데이터 - 옵저버 패턴 관련
implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version")
}
다음으로 ViewModel()을 상속받는 NumberViewModel이라는 클래스를 만든다.
class NumberViewModel : ViewModel() {
// MutableLiveData - 수정 가능한 데이터
// LiveData - 값이 변경 안됨
// 내부에서 설정하는 자료형은 Mutable로
// 변경가능하도록 설정
private val _currentValue = MutableLiveData<Int>()
val currentValue : LiveData<Int>
get() = _currentValue
//초기값 설정 (Mutable 이기 때문에 값을 넣을 수 있음)
init {
Log.d(TAG, "myNumberViewModel - 생성자 호출")
_currentValue.value = 0
}
fun updateValue(actionType : ActionType, input: Int){
when(actionType){
ActionType.PLUS ->
_currentValue.value = _currentValue.value?.plus(input)
ActionType.MINUS ->
_currentValue.value = _currentValue.value?.minus(input)
}
}
NumberViewModel 클래스 안에 backing properties를 활용하여 값이 변경되는 것은 NumberViewModel 클래스 안에서만 사용 가능하므로 _를 넣어주어 MutableLiveData 타입인 _currentValue 멤버 변수를 만들어 준다.
외부에서는 값을 변동 못하도록 currentValue 멤버 변수를 LiveData타입으로 설정한다.
get()으로 MutableLiveData인 _currentValue를 받는다.(backing properties는 추후에 정리해보자)
후에 초기값을 설정하고 updateValue라는 뷰모델이 가지고 있는 값을 변경하는 메소드를 만들어준다.
class MainActivity : AppCompatActivity(), View.OnClickListener {
lateinit var numberViewModel : NumberViewModel
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 뷰 모델 프로바이더를 통해 뷰모델 가져오기
// 라이프사이클을 가지고 있는 녀석을 넣어줄 즉 자기 자신
// 우리가 가져오고 싶은 뷰모델 클래스를 넣어서 뷰모델을 가져오기
numberViewModel = ViewModelProvider(this).get(NumberViewModel::class.java)
// 뷰모델이 가지고 있는 값의 변경사항을 관찰할 수 있는 라이브 데이터를 옵저빙함.
numberViewModel.currentValue.observe(this, Observer {
binding.tvNum.text = it.toString()
})
//버튼 클릭시 PLUS, MINUS
binding.btnMinus.setOnClickListener(this)
binding.btnPlus.setOnClickListener (this)
}
override fun onClick(v: View?) {
val userInput : Int = binding.etUserInput.text.toString().toInt()
when(v){
binding.btnPlus ->
numberViewModel.updateValue(actionType = ActionType.PLUS, userInput)
binding.btnMinus ->
numberViewModel.updateValue(actionType = ActionType.MINUS, userInput)
}
}
}
ViewModelProvider를 통해서 MainActivity와 NumberViewModel을 연결시켜주고 NumberViewModel에 저장되는 UI 데이터를 MainActivity로 가져온다.
ViewModelProvider에 context를 넣어주고, get으로 Viewmodel 클래스를 가져온다.
다음으로 LiveData의 Observer를 활용해서 값이 변경되는 것을 관찰하도록 한다.
이상 ViewModel을 활용하는 것까지 작성해봤다. ViewModelProvider나 ViewModelStore에 관한 다른 내용도 있고, 프래그먼트 데이터 공유 및 코루틴을 활용한 ViewModel도 있는데 이와 같은 내용은 LiveData나 DataBinding같은 다른 파트를 공부해보고 다시 공부해서 작성해보겠다!!! 빠샤😤