반응형
ViewModel
- ViewModel은 Activity 및 Fragment의 생명주기를 고려하여 UI 관련 데이터를 저장하고 관리한다.
- Activity는 Activity가 끝날 때까지 그리고 Fragment는 Fragment가 분리될 때까지 데이터를 메모리에서 유지한다.
- Activity 및 Fragment의 생명 주기와 관계없이 데이터를 유지한다.
- 화면 회전과 같이 구성을 변경 할때도 데이터를 유지할 수 있다.
ViewModel lifecycle
- Activity의 onCreate()메서드에 처음 호출할 때 ViewModel을 요청 및 생성한다.
- 시스템은 액티비티 기간 내내(예: 기기 화면이 회전될 때) onCreate() 메서드를 여러 번 호출한다.
- ViewModel은 액티비티가 소멸될 때까지 존재한다.
LiveData
- LiveData는 Observable data holder class, 즉 데이터를 저장하고 변화를 관찰할 수 있는 객체다.
- UI객체는 LiveData에 옵저버(observer)를 등록할 수 있으며, 데이터가 변경될 때 UI 갱신한다.
- LiveData가 보유하고 있는 데이터에 변화가 일어나면, LiveData는 등록된 옵저버 객체에 변화를 알려주고, 옵저버 객체의 onChanged() 메서드를 실행한다.
- LiveData는 컴포넌트의 생명주기를 알고 있기 때문에 액티비티가 onStart(), onResume() 상태일 때는 콜백을 실행하고, 다른 액티비티로 넘어가는 onStop()일 때는 실행하지 않는다.
- 즉, 컴포넌트들의 생명주기 상태가 activity(활성화) 상태일 때만 data에 대한 update를 제공한다.
LiveData 객체 생성
- LiveData는 객체 뿐만 아니라 모든 데이터와 함께 사용할 수 있는 래퍼(wrapper) 객체로서, ViewModel 객체 에 저장되며, getter/setter 메서드를 통해 액세스를한다.
LiveData 형식
- MutableLiveData: 값의 변경/읽기 모두 가능한 LiveData (게터, 세터 모두 사용 가능)
- MutableLiveData 형식의 LiveData를 변경할 때는 setValue(), postValue() 메서드를 사용한다.
- setValue(): Main 스레드에서 즉시 값을 변경하고, 옵저버로 데이터 변경을 알려준다.
- postValue(): 백그라운드 스레드에서 라이브데이터를 변경하고 싶을 때 사용한다.
내부적으로 핸들러를 통해 Main Looper 보내기 때문에 실제 변경은 Main 스레드에서 처리한다고 한다.
- LiveData: 값의 변경이 불가능하여 읽기만 가능한 LiveData (게터만 사용 가능)
LiveData 객체 Observe
- LiveData는 데이터가 변경될 때만 액티비티의 관찰자에게 업데이트를 전달한다.
- ViewModel 가져오기
- LiveData에 변화가 발생했을 LiveData를 가져와서 처리할 로직을 정의하는 Observer 객체를 생성한다.
- observe() 메서드를 사용하여 LiveData 객체에 Observer 객체를 연결한다.
- observe(LifecycleOwner, Observer)
- LifeCycle: 컴포넌트의 생명주기 상태정보를 가지는 객체
- LifeCycleOwner: LifeCycle 객체에 컴포넌트의 생명주기 상태정보를 제공하는 객체
Activity에서는 곧 자신이기 때문에 this, Fragment는 viewLifecycleOwner로 전달,
LiveData는 LifecycleOwner를 통해 해당 View의 Lifecycle을 관찰한다. - Observer: 변경된 LiveData를 수신하여 처리할 로직을 정의하는 인터페이스로 LiveData 객체를 구독하여 변경사항의 알림을 받는 객체다.
예제코드
이제 예제 코드로 본격적인 사용법을 살펴보자.
아래는 layout viewmodel_examl.xml 코드다.
<?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/counter_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="80sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:clickable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/ic_add_black_24dp" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_remove"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginBottom="16dp"
android:clickable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@drawable/ic_remove_black_24dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
다음은 사용할 ViewModel 클래스다.
class ViewModelMainActivity : AppCompatActivity() {
private val binding by lazy{
ViewmodelExamBinding.inflate(layoutInflater)
}
//ViewModel 가져오기
//ViewModelProvider(context).get(ViewModel class)
private val viewModel by lazy{
ViewModelProvider(this).get(CounterViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
val counterObserver = Observer<Int>{
binding.counterTv.text = it.toString()
}
viewModel.getCounter().observe(this,counterObserver)
init()
}
fun init(){
with(binding){
binding.fabAdd.setOnClickListener{
viewModel.increaseOrDecreaseValue(1)
}
binding.fabRemove.setOnClickListener{
viewModel.increaseOrDecreaseValue(-1)
}
}
}
}
마지막은 액티비티 클래스다.
class ViewModelMainActivity : AppCompatActivity() {
private val binding by lazy{
ViewmodelExamBinding.inflate(layoutInflater)
}
//ViewModel 가져오기
//ViewModelProvider(context).get(ViewModel class)
private val viewModel by lazy{
ViewModelProvider(this).get(CounterViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
val counterObserver = Observer<Int>{
binding.counterTv.text = it.toString()
}
viewModel.getCounter().observe(this,counterObserver)
init()
}
fun init(){
with(binding){
binding.fabAdd.setOnClickListener{
viewModel.increaseOrDecreaseValue(1)
}
binding.fabRemove.setOnClickListener{
viewModel.increaseOrDecreaseValue(-1)
}
}
}
}
그럼 이제 아래와 같이 에뮬레이터가 동작하고 에뮬레이터의 화면을 좌우로 돌려도 값이 변하지 않는 것을 확인할 수 있다.
이상으로 포스팅을 마칩니다. 감사합니다!
참고자료
https://developer.android.com/topic/libraries/architecture/viewmodel?hl=ko
반응형
댓글