안드로이드에서 MVVM의 구현을 위해 View Model, LiveData 을 사용해보고자 한다.
※ View Model 이란?
MVVM 패턴에서 수명주기를 고려하여 데이터를 저장하고 관리한다.
회전 등의 화면 변화가 있을때에도 데이터를 유지한다.
View Model 객체는 자동 보관되어 다른 activity나 fragment에서도 사용할 수 있다.
View Model을 사용하면 화면이 변경될 때 onSaveInstanceState() 등의 활용 없이 데이터를 보관하여 유지할 수 있다.
뷰모델은 액티비티가 종료될 때 까지 유지된다.
하지만 Lifecycle 또는 view, activity context를 참조하면 메모리 누수가 발생하므로, 이러한 객체를 참조해서는 안된다.
또한 LiveData와 같은 LifecycleObserver들을 포함할 수 있지만, LiveData와 같이 수명 주기를 인식하는 Observable의 변경사항을 관찰하면 안 된다.
context가 필요하다면 AndroidViewModel을 상속하여 application 생성자를 사용하면 된다.
즉, ViewModle은생명주기의 영향을 받지 않고 데이터를 유지, 보관하기 위해 사용한다고 볼 수 있다.
또한 UI와 컨트롤러가 분리되고, 다른 액티비티와 프래그먼트 간의 데이터 공유가 쉬워지는 장점도 있다.
※ LiveData 란?
관찰자 (액티비티, 프래그먼트) 의 생명주기를 알고 있는 간단한 observable
observer 패턴을 구현하기 위하여 사용됨.
보통 ViewModel 과 함께 사용된다.
수명주기를 수동으로 처리하지 않아도 되고, 메모리 누수가 사라진다.
사용해보기
dependencies 추가
def lifecycle_version = "2.3.0"
// ViewModel - 라이프 사이클
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData - 데이터의 변경 사항을 알 수 있음
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
dependencies 에 뷰모델과 라이브데이터를 추가해준다.
레이아웃 만들기
그 후 임의의 레이아웃을 만들어준다.
UP 을 누르면 텍스트뷰의 숫자가 올라가고, DOWN 을 누르면 숫자가 내려가는 간단한 테스트를 해보고자 한다.
ViewModel 추가
enum class actionType{
UP, DOWN
}
// 데이터의 변경사항을 알려주는 라이브 데이터를 가지는 뷰모델
class MainViewModel : ViewModel() {
// 변경가능한 Mutable 타입의 LiveData
private val _currentValue = MutableLiveData<Int>()
// 무결성을 위한 Getter
val currentValue : LiveData<Int>
get() = _currentValue
// 초기값
init{
_currentValue.value = 0
}
// Setter
fun updateValue(type : actionType){
when(type){
actionType.UP ->
_currentValue.value = _currentValue.value?.plus(1)
actionType.DOWN ->
_currentValue.value = _currentValue.value?.minus(1)
}
}
}
ViewModel을 상속하는 클래스를 생성해주고,
옵저버 패턴을 위한 MutableLiveData를 생성해준다.
이 currentValue가 메인에서 텍스트뷰에 사용할 값이다.
udateValue에서 UP 을 받을때마다 1을 증가시키고, DOWN을 받을때마다 1을 감소시킨다.
MainAcitivity
class MainActivity : AppCompatActivity(), View.OnClickListener {
lateinit var mainViewModel : MainViewModel
lateinit var button1 : Button
lateinit var button2 : Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val text = findViewById<TextView>(R.id.numberText)
button1 = findViewById(R.id.button)
button2 = findViewById(R.id.button2)
button1.setOnClickListener (this)
button2.setOnClickListener (this)
// 뷰모델 가져오기
mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
// 관찰하여 데이터 값이 변경되면 호출
mainViewModel.currentValue.observe(this, Observer {
text.text = it.toString()
})
}
override fun onClick(p0: View?) {
when(p0){
button1 ->
mainViewModel.updateValue(actionType.UP)
button2 ->
mainViewModel.updateValue(actionType.DOWN)
}
}
}
ViewModelProvider로 뷰모델을 가져오고
observe로 현재값을 관찰하여 바뀔때마다 텍스트뷰에 할당해준다.
잘 구현되었다.
❗ 단, 여기서 주의할 점이 있다.
여기서는 옵저버패턴을 메인액티비티에서 구현하였기 때문에 문제가 없지만,
프래그먼트에서 옵저버 패턴을 구현할 경우 화면 회전등의 경우에 프래그먼트는 생명주기가 다르기 때문에 onDestroy를 거치지 않아서 중복으로 옵저버 구독이 되는 일이 발생한다.