본문 바로가기

Android/Basic

[Android: Jetpack] LiveData & Observer Pattern

💬
인프런 ‘냉동코더의 알기 쉬운 Modern Android Development 입문’을 수강하며 정리한 핵심내용과 별도로 공부를 진행하며 작성한 내용입니다.

LiveData


  • 값의 변경을 감지할 수 있는 데이터 홀더이다.
  • ViewModel과 결합할 때 시너지 효과

    [기존]

    • UI에 표시할 데이터를 ViewModel에 저장한다.
    • 값이 변경되면 ViewModel의 데이터를 변경한다.
    • Activity에서 ViewModel에 접근해서 변견된 값을 다시 화면에 표시한다.

    → 별도의 생명주기를 가진 ViewModel을 사용해 액티비티 재생성 시에도 ui 값 유지 가능

    [LiveData사용 시]

    • 데이터 홀더로 LiveData를 사용
    • 값의 변경을 감지해서 UI에 자동으로 반영 가능해진다.

    → 이것이 MVVM에서 구현해야 하는 ViewModel의 개념이다.

Observer Pattern


디자인 패턴의 한 종류이다.

🤔
디자인 패턴 vs 아키텍쳐 패턴
  • 아키텍쳐 패턴

    프로그램 내에서 큰 구조로 구성되어 다른 요소들을 관리하는 역할을 한다.

  • 디자인 패턴

    특정 유형의 문제를 해결하는 방법으로 아키텍처보다 좁은 개념이다.

  • 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
      	}
      })

  • 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 사용하기


  1. dependancy 추가
    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1'
  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를 사용한다.
  1. 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 적용하기


  1. 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"
        }
    }
  1. Activity 수정
    myViewModel.modifiedCounter.observe(this) { counter ->
    	binding.textView.text = counter.toString()
    }


Uploaded by N2T