해당 게시물은 혼자 공부하면서 배운 내용을 토대로 작성한 글이므로 틀린 내용이 있을 수 도 있습니다. 틀린 내용이 있을 경우 지적해주시면 감사하겠습니다.
ViewModel과 LiveData를 이용해 간단한 덧셈 뺄셈 연산을 하고 UI에 결과를 업데이트 하는 앱을 만들어 봤습니다.
ViewModel 은 Android Jetpack 의 구성요소 중 하나로 소프트웨어 개발 디자인 패턴 중 하나인 MVVM(Model-View-ViewModel) 에서 파생되었다.
ViewModel 은 View 로부터 독립적이고, View가 필요로하는 데이터를 가지고 있다.
앱 개발시 MVVM 패턴을 적용하면, UI 컨트롤러 (Activity, Fragment) 의 과도한 책임을 분담해서 코드가 방대해지는 것을 방지하고, 프로그램을 유지/보수 하는데 용이하다.
위 그림을 보면 Activity 의 생명주기와 ViewModel 의 생명주기를 같이 볼 수 있다.
ViewModel 은 Activity 나 Fragment 가 완전히 종료되기 전까지 메모리 상에 존재한다.
Activity rotated(화면이 회전 되었을 때) 앱이 onPause 상태에서 다시 시작하는 것을 확인 할 수 있다. 하지만, ViewModel 은 여전히 메모리 상에 남아 있다.
LiveData 라는 데이터 홀더 클래스가 가지고 있는 데이터가 변경되는지 감시하고 변경되면 UI 컨트롤러에게 알려준다.
데이터 변경을 감시하는 역할은 LiveData가 가지고 있는 Observer(관찰자) 라는 녀석이 해준다.
ViewModel 을 사용하기 위해서 다음 의존성을 추가 해야한다.
def lifecycle_version = "2.4.0" implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
<?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/numberTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/inputEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/plusButton"
app:layout_constraintTop_toBottomOf="@id/numberTextView"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:layout_marginTop="10dp"/>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/plusButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="더하기"
app:layout_constraintTop_toBottomOf="@id/numberTextView"
app:layout_constraintEnd_toStartOf="@id/minusButton"
android:layout_marginEnd="10dp"
android:layout_marginTop="10dp"/>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/minusButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="빼기"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/numberTextView"
android:layout_marginEnd="10dp"
android:layout_marginTop="10dp"/>
<TextView
android:id="@+id/testValueTextView"
android:layout_width="300dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/plusButton"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="20dp"
android:layout_marginStart="10dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
ViewModel() 을 상속받는 클래스를 생성해줍니다.
이곳에서 View 의 데이터를 관리합니다.
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
enum class ActionType{
PLUS, MINUS
}
// 데이터의 변경
// 뷰모델은 데이터의 변경사항을 알려주는 라이브 데이터를 가지고 있음
class MyNumberViewModel: ViewModel(){
// 뮤터블 라이브 데이터 - 수정 가능
// 라이브 데이터 - 수정 불가능
companion object{
private const val TAG = "로그"
}
private val _currentValue = MutableLiveData<Int>()
// LiveData 데이터로 리스트를 사용할 수도 있는지 테스트 해보기 위해서 작성했습니다.
private val _testValue = MutableLiveData<MutableList<Int>>()
private val testList = mutableListOf<Int>()
// 변경되지 않는 데이터를 가져 올 때 이름을 언더바(_) 없이 설정
// 공개적으로 가져오는 변수는 private 이 아닌 퍼블릭으로 외부에서도 접근 가능하도록 설정
// 하지만 값을 직접 라이브데이터에 접근하지 않고 뷰모델을 통해 가져올 수 있도록 설정
val currentValue : LiveData<Int>
get() = _currentValue
val testValue : LiveData<MutableList<Int>>
get() = _testValue
// 초기값 설정
init{
Log.d(TAG, "MyNumberViewModel - 생성자 호출")
_currentValue.value = 0
}
// 뷰모델이 가지고 있는 값을 변경시키는 메서드
fun updateValue(actionType: ActionType, input: Int){
// testList 에 input 을 추가하고
testList.add(input)
// _testValue 를 대체합니다.
_testValue.value = testList
when(actionType){
// 인자로 들어온 actionType 을 확인
ActionType.PLUS ->
_currentValue.value = _currentValue.value?.plus(input)
ActionType.MINUS ->
_currentValue.value = _currentValue.value?.minus(input)
}
}
}
ViewModelProvider를 통해 뷰 모델을 생성하고 currentValue 에 observer 를 등록해서 데이터가 변경되는지 감시합니다.
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import com.dldmswo1209.jetpacktest.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity(), View.OnClickListener {
companion object{
private const val TAG = "로그"
}
lateinit var myNumberViewModel: MyNumberViewModel
private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
// 뷰모델 가져오기
myNumberViewModel = ViewModelProvider(this)[MyNumberViewModel::class.java]
// LiveData 의 관찰자 observe
myNumberViewModel.currentValue.observe(this, Observer{
Log.d(TAG, "MainActivity - myNumberViewModel - currentValue 라이브 데이터 값 변경 : $it")
binding.numberTextView.text = it.toString()
})
myNumberViewModel.testValue.observe(this, Observer {
Log.d(TAG, "MainActivity - myNumberViewModel - testValue 라이브 데이터 값 변경 : $it")
binding.testValueTextView.text = it.toString()
})
binding.plusButton.setOnClickListener(this)
binding.minusButton.setOnClickListener(this)
}
override fun onClick(view: View?) {
val userInput = binding.inputEditText.text.toString().toInt()
when(view){
binding.plusButton->
myNumberViewModel.updateValue(actionType = ActionType.PLUS, userInput)
binding.minusButton->
myNumberViewModel.updateValue(actionType = ActionType.MINUS, userInput)
}
}
}
https://www.youtube.com/watch?v=-b0VNKw_niY
https://developer.android.com/topic/libraries/architecture/viewmodel