ViewModel이란?
ViewModel이란 Android Jepack의 구성요소 중 하나로,
Activity와 Fragment 같은 UI Controller의 수명 주기를 관리합니다.
또한 본래 ViewModel이란 이름은 소프트웨어 개발 디자인 패턴 중 하나인
MVVM(Model — View — ViewModel) 디자인 패턴으로부터 파생되었습니다.
MVVM의 관점에서 부르는 ViewModel과
Android Jetpack에 포함된 ViewModel 클래스를 구분하기 위해
Android Jetpack에 포함된 ViewModel을 Android Architecture ViewModel(AAC)이라 합니다.
ViewModel을 사용하는 이유?
ViewModel은 Activity 또는 Fragment의 생명 주기보다 더 긴 생명주기를 가지고 있습니다.
액티비티가 최초 생성될 때 일반적으로 ViewModel을 인스턴스화 하여 생명주기를 함께 시작합니다.
- Activity -> Activity가 완전히 종료될 때까지 유지.
- Fragment -> Fragment가 분리될 때까지 메모리에 남아있도록 설계.
위의 그림을 보면 Activity의 생명주기와 ViewModel의 생명주기를 함께 확인할 수 있습니다.
ViewModel의 목적은 UI 컨트롤러의 데이터를 캡슐화하여 구성이 변경되어도 데이터를 유지하는 것입니다.
하지만 Activity와 Fragment와 같은 UI Controller는 파괴될 때 연관된 데이터를 잃습니다.
ViewModel은 위에서 말했듯 Activity가 더 이상 사용하지 않는 상태가 되었을 때
onCleared()를 호출한 후 내부 데이터를 초기화하고 파괴합니다.
아주 긴 생명주기를 가지고 있으므로 이를 사용해 데이터를 유지하도록 하는 게
ViewModel의 목적입니다.
그럼 예시를 통해 한 번 ViewModel를 사용해보도록 하죠!
아래의 예시는 ViewModel을 사용하지 않은 경우의 상황입니다.
ViewModel 없을 때 예시
activity_main.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">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:id="@+id/count_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:gravity="center"
android:inputType="number"
android:textSize="100sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="0" />
<Button
android:id="@+id/button_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="add"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/count_edit_text" />
<TextView
android:id="@+id/text_sum"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/purple_200"
android:textSize="50sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button_count"
tools:text="0" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
MainActivity.kt
package com.app.databinding
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import com.app.databinding.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
// 더하기 결과를 받을 변수
var total : Int = 0
// total에 더할 숫자를 받을 변수
var a : Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this,R.layout.activity_main)
binding.textSum.text = total.toString()
binding.buttonCount.setOnClickListener{
a = binding.countEditText.text.toString().toInt()
total += a
binding.textSum.text = total.toString()
}
}
}
이런 식으로 코드를 친 후 한 번 실행을 해보겠습니다.
어..? 화면을 돌렸더니 값이 사라졌네요.
이러한 현상이 왜 일어날까요?
위에서도 말했듯 이런 현상이 일어나는 이유는 생명주기 때문입니다.
화면 회전이 이루어지면 Destroy 되었다가 다시 Create 되기 때문에
기존의 데이터가 날아가는 것이죠.
ViewModel을 사용하지 않고도 해결이 가능합니다.
saveInstanceState를 사용해 액티비티가 파괴되기 전
save 하고 싶은 데이터를 saveInstanceState를 사용해 onCreate로 넘기면 됩니다.
하지만 이 방법에는 여러 문제가 있습니다.
담을 수 있는 데이터가 적다든지,
담을 수 있는 데이터의 형태가 한정되어있다던지 등!
그러므로 우리는 이제 ViewModel를 사용하여
데이터가 사라지지 않게 만들 겁니다.
자 드디어 사용을 해봅시다.
ViewModel 사용 예시
ViewModel을 사용하지 않았을 경우의 XML은 그대로 가지고 갈 겁니다.
새로운 클래스를 추가할 거예요.
MainActivityViewModel 이라는 class를 생성해보죠.
MainActivityViewModel.kt
package com.app.databinding
import androidx.lifecycle.ViewModel
class MainActivityViewModel : ViewModel() {
// 결과를 받을 변수, 초기 결과는 0
private var total = 0
// 결과값을 return 하는 함수
fun getTotal(): Int{
return total
}
// 숫자를 더할 때 사용할 함수
fun setTotal(input : Int){
total += input
}
}
결과를 받을 변수를 total로 지정했고
getTotal()과 setTotal()을 이용해
숫자를 더하고 결과를 보여줄 겁니다.
이제 MainActivity를 살펴보죠.
MainActivity.kt
package com.app.databinding
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import com.app.databinding.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
// 나중에 정의한 변수들을 lateinit var로 선언
lateinit var binding: ActivityMainBinding
lateinit var viewModel: MainActivityViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// databinding 적용.
binding = DataBindingUtil.setContentView(this,R.layout.activity_main)
// viewModel의 주최를 MainActivityViewModel로 설정
viewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)
// getTotal()를 호출해 현재 total(현재 값 : 0)을 불러온다.
binding.textSum.text = viewModel.getTotal().toString()
binding.buttonCount.setOnClickListener{
// setTotal()을 호출해 edittext의 값을 total에 더한다.
viewModel.setTotal(binding.countEditText.text.toString().toInt())
// 현재 값을 불러온다.
binding.textSum.text = viewModel.getTotal().toString()
}
}
}
viewModel 변수를 선언했습니다.
저는 MainActivityViewModel을 사용해야 하니 메인 액티비티에서 사용할 뷰 모델 클래스를 받아옵니다.
현재 값을 getTotal()을 통해 0으로 세팅하고
setTotal()을 통해 edittext의 값을 total에 더해 합계를 출력하는 코드입니다.
그럼 이제 앱을 실행해볼까요?
이제 액티비티가 파괴되어도 데이터가 살아있네요!
ViewModel의 onCleared()는 앱이 백그라운드에 놓이고 시스템 메모리를 확보하기 위해
앱 프로세스가 종료될 때 호출됩니다. 그러므로 값이 사라질 일이 없죠.
어우! 너무 좋네요! 이제 뷰모델만 사용해야겠어요.
마치며
오늘 ViewModel에 대해 알아보았는데요, ViewModel은 많은 곳에 쓰이기 때문에 다른 글을 보면서 응용하는 방법에 대해 알게 되셨으면 좋겠습니다. 긴 글 읽어주셔서 감사합니다.
https://developer.android.com/topic/libraries/architecture/viewmodel?hl=ko