LiveData
- 값의 변경을 감지할 수 있는 데이터 홀더이다.
- ViewModel과 결합할 때 시너지 효과
[기존]
- UI에 표시할 데이터를 ViewModel에 저장한다.
- 값이 변경되면 ViewModel의 데이터를 변경한다.
- Activity에서 ViewModel에 접근해서 변견된 값을 다시 화면에 표시한다.
→ 별도의 생명주기를 가진 ViewModel을 사용해 액티비티 재생성 시에도 ui 값 유지 가능
[LiveData사용 시]
- 데이터 홀더로 LiveData를 사용
- 값의 변경을 감지해서 UI에 자동으로 반영 가능해진다.
→ 이것이
MVVM
에서 구현해야 하는ViewModel
의 개념이다.
Observer Pattern
디자인 패턴의 한 종류이다.
- subject(ex: LiveData)의 상태 변화를 관찰하는 Observer들을 객체와 연결
- subject의 상태 변화를 초래하는 Event가 발생하면, 객체가 그 Event를 직접 Observer에게 통지한다.
→ LiveData를 사용하는 것은 observer 패턴을 구현하는 것과 동일하다!
Observable vs LiveData
Observer에 의해 값의 변경을 감지할 수 있는 안드로이드 데이터 홀더로는 observable과 livedata가 있다.
✅ observable field와 livedata를 구분하는 가장 큰 차이점은?
- Observable
- 기본형 observable이 존재한다.
- ex) observableBoolean, observableByte, observableChar… etc.
- 특수 타입을 위한 ovservable
ObservableField<타입>
을 통해 구현할 수 있다.
private val observalbeString = ObservableField<String>("Default value") //✅콜백을 등록하는 과정 observableString.addOnPropertyChangedCallback(object: Observable.OnPropertyChangedCallback(){ override un onPropertyChanged(sender: Observalbe?, propertyId: Int){ ...// To Do Something } })
- 기본형 observable이 존재한다.
- LiveData
private val observableString = MutableLiveData<String>("Default value") //✅lifecycleOwner를 전달하는 과정 observableString.observe(lifecycleOwer, observer{ ...// To Do Something })
observable: 콜백 등록
- lifecycle을 알 수 없으므로 등록한 콜백이 상시 작동되어야 한다.
- 작동이 필요 없어지면 removeOnPropertyChangedCallback을 호출하여 콜백을 수동으로 제거해야 한다.
LiveData: lifecycleOwner 전달
- lifecycle이 STARTED 혹은 RESUME으로 활성화 상태인 경우에만 observe를 수행한다.
- 나머지 상태에서는 자동으로 observe가 비활성화된다.
⇒ observable을 lifecycle과 연동한다.
🌟 LiveData의 이점
- UI 데이터 상태의 일치 보장
- 메모리 누수가 없음
- 중지된 활동으로 인한 비정상 종료가 없음
- 수명 주기를 더 이상 수동으로 처리하지 않음
- 최신 테이터 유지
- 적잘한 구성 변경
- 리소스 공유
LiveData 사용하기
- dependancy 추가
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1'
- ViewModel의 데이터에서 LiveData사용하기
class MyViewModel( _counter: Int, private val savedStatedHandle: SavedStateHandle ): ViewModel() { var liveCounter: MutableLiveData<Int> = MutableLiveData(_counter) var counter = savedStatedHandle.get<Int>(SAVE_STATE_KEY) ?: _counter fun saveState() { savedStatedHandle.set(SAVE_STATE_KEY, counter) } companion object{ private const val SAVE_STATE_KEY = "counter" } }
- livedata는 값을 변경할 수 없다.
- 그래서, 값 변경을 위해서
mutableLiveData
를 사용한다.
- Activity에서 LiveData의 Observer등록하기
binding.button.setOnClickListener { myViewModel.liveCounter.value = myViewModel.liveCounter.value?.plus(1) //⭐ 이벤트에 따라 livedata 값을 변경 } myViewModel.liveCounter.observe(this) { counter -> //⭐ liveData observer 등록하기 binding.textView.text = counter.toString() //⭐ UI 업데이트 로직 구현 }
Transformation
하나의 LiveData가 변경될 때마다 또 다른 LiveData가 변경될 때를 생각해보자.
val userLiveData: MutableLiveData<User> = MutableLiveData<User>
val userNameLiveData: LiveData<String> = Transformations.map(userLiveData){
user.firstName + " " + user.lastName
}
- userData의 값이 이벤트에 의해 바뀐다.
- 바뀐 userData에 따라서 새로운
userNameLiveData
값이 생성된다.
→ 즉, 하나의 liveData가 변경될 때 다른 LiveData값이 변경되는 상황이다.
ViewModel이 데이터 원본을 조작하게 하면, 뷰모델과 데이터의 결합관계가 강해지는 문제가 발생하는 것이다.
따라서, 이를 해결하기 위해 사용하는 것이 Tranformations이다.
Transformations
- 전달받은 LiveData의 변경이 일어난다.
- 람다함수를 실행시킨다.
- 새로운 값의 liveData를 반환한다.
- 원본 데이터의 변경 없이 새로운 liveData를 만들어 사용한다.
- 반환되는 새로운 liveData를 관할하는 observer가 없다면, 람다함수가 실행되지 않는다.
- Map
LiveData<Y> map(LiveData<X> source, Function<X,Y>func)
- 두 번째 인자함수의 반환값은 제안이 없다.
val userLiveData = .... val userNameLiveData: LiveData<String> = Transformations.map(userLiveData) { user-> user.firstName + user.lastName }
- SwitchMap
LiveData<Y> switchMap(LiveData<X>trigger, Function<X,LiveData<Y>>func)
- 두 번째 인자 함수의 반환값이 LiveData여야 한다.
val nameQueryLiveData = ... val nameQueryLiveData: LiveData<User> = Transformations.switchmap(nameQueryLiveData) {name -> myDataSource.getUserSwitchNameLiveData(name) } fun setNameQuery(name: String) { nameQueryLiveData.setValue(name); }
Transformations.map 적용하기
- ViewModel 수정
class MyViewModel( _counter: Int, private val savedStatedHandle: SavedStateHandle ): ViewModel() { var liveCounter: MutableLiveData<Int> = MutableLiveData(_counter) var counter = savedStatedHandle.get<Int>(SAVE_STATE_KEY) ?: _counter val modifiedCounter: LiveData<String> = Transformations.map(liveCounter) { counter -> "$counter 입니다." } fun saveState() { savedStatedHandle.set(SAVE_STATE_KEY, counter) } companion object{ private const val SAVE_STATE_KEY = "counter" } }
- Activity 수정
myViewModel.modifiedCounter.observe(this) { counter -> binding.textView.text = counter.toString() }
Uploaded by N2T
'Android > Basic' 카테고리의 다른 글
[Android: Basic] Notification (0) | 2023.08.23 |
---|---|
Moshi Converter (0) | 2023.02.25 |
[Android: Jetpack] Architecture Pattern과 Android App Architecture (1) | 2023.01.12 |
[Android: Jetpack] ViewModel & Lifecycle 기초(2) (0) | 2022.12.31 |
[Android: Jetpack] ViewModel & Lifecycle 기초(1) (0) | 2022.12.31 |