오늘 알아볼 ViewModel은 Android JetPack의 구성요소 중 하나이다.
ViewModel이란 이름은 소프트웨어 개발 디자인 패턴 중 하나인 MVVM(Model-View-ViewModel) 디자인 패턴으로부터 파생되었다.
MVVM의 관점에서 부르는 ViewModel과 Android Jetpack에 포함된 ViewModel클래스를 구분하기 위해 Android Jetpack에 포함된 ViewModel을 Android Architecture ViewModel의 약자인 AAC ViewModel이라고도 부른다.
Activity와 fragment와 같은 UI 컨트롤러의 로직에서 데이터를 다루는 로직을 분리
하기 위해 등장한 Android JetPack 라이브러리이다.
데이터를 표시해주거나, 사용자가 어떤 작업을 했을 때 반응을 보여주거나, 권한 요청과 같은 OS 커뮤니케이션을 처리하는 것이 UI 컨트롤러의 목적이다.
따라서, UI 컨트롤러에서 데이터를 다루는 로직을 책임지게 되면 많은 유지보수가 필요한 비동기 호출과 같은 작업들을 해야하기 때문에 UI 컨트롤러에 과도한 책임이 생기게 된다.
UI 컨트롤러에서는 생명주기에 따라 앱이 활동중에 예를 들어 화면을 회전했을 경우 화면을 갱신하면서 데이터가 초기화 된다.
데이터를 복원하기 위해 onSaveInstanceState()
메서드를 사용하는 방법은 작은 용량의 데이터에만 적합하고 데이터가 커지게 되면 적합하지 않다.
MVVM의 관점에서 봤을 때 ViewModel은 View로부터 독립적이며, View가 필요로 하는 데이터만을 소유한다.
안드로이드 앱 개발시에도 MVVM 디자인 패턴을 적용하면 Activity나 Fragment 같은 UI 컨트롤러의 과도한 책임을 분담하여 클래스가 거대해지는 것을 방지하고, 유지보수, 재사용성 그리고 테스트 등을 용이하게 만들어 준다.
구글에서도 앱 개발자들에게 MVVM패턴 사용을 권장하고 있다.
안드로이드 생명주기를 관리하기 쉽기 때문이다.
MVVM관점의 ViewModel을 구현할 때 AAC ViewModel을 사용하면 좋다.
Activity에서는 Activity가 완전히 종료될 때까지,
Fragment에서는 Fragment가 분리될 때까지 메모리에 남아있도록 설계되어 있다.
ViewModel의 Scope(생명주기의 범위)는 ViewModel을 가져올 때 ViewModelProvider에 의해 결정 된다.
Activity의 finish() 호출 등에 의해 Activity 생명주기가 종료됨에 따라 내부의 LifecycleEventObserver를 통해 ViewModel도 onCleared() 콜백 메서드를 호출하고 종료된다.
ViewModel을 이용하여 + 버튼
와 - 버튼
으로 숫자를 카운트하는 앱을 만들어보겠다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textSize="50dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/plus"
android:text="+"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/minus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="-"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
class MainViewModel : ViewModel() {
var countValue = 0
init {
Log.d("MainViewModel", "init")
}
fun plus(){
countValue++
}
fun minus(){
countValue--
}
fun getCount() : Int {
return countValue
}
}
ViewModelProvider는 ViewModel 객체를 생성하기 위해 사용하는 클래스이다. Activity나 Fragment에서는 ViewModelProvider를 통해 자기 자신을 생성자로 전달하여 ViewModel 인스턴스를 획득해야 한다.
class MainActivity : AppCompatActivity() {
lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// ViewModel 인스턴스 생성
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
val plusBtn : Button = findViewById(R.id.plus)
val minusBtn : Button = findViewById(R.id.minus)
val resultArea : TextView = findViewById(R.id.result)
// 화면 처음 갱신 될 때 초기값 보여주기
resultArea.text= viewModel.countValue.toString()
plusBtn.setOnClickListener{
// 카운트 값 텍스트뷰에 보여주기
viewModel.plus()
}
minusBtn.setOnClickListener{
// 숫자 감소
viewModel.minus()
// 카운트 값 텍스트뷰에 보여주기
resultArea.text= viewModel.countValue.toString()
}
}
}
참고
https://www.google.com/url?sa=i&url=https%3A%2F%2Fzsmb.co%2Ffragment-lifecycles-in-the-age-of-jetpack%2F&psig=AOvVaw34bHyO_U-jCcumpB4I1L-0&ust=1683537494054000&source=images&cd=vfe&ved=0CBEQjRxqFwoTCOjXl4fw4v4CFQAAAAAdAAAAABAP https://charlezz.medium.com/viewmodel%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-viewmodel-%EC%B4%88%EB%B3%B4%EB%A5%BC-%EC%9C%84%ED%95%9C-%EA%B0%80%EC%9D%B4%EB%93%9C-e1be5dc1ac18
https://www.google.com/url?sa=i&url=https%3A%2F%2Fvelog.io%2F%40pachuho%2FAndroid-Docs-MVVM-1%25ED%258E%25B8&psig=AOvVaw02rjwKlrQM53ik_o9lPnJN&ust=1683540094031000&source=images&cd=vfe&ved=0CBEQjRxqFwoTCNCnleL54v4CFQAAAAAdAAAAABAP