[안드로이드] LiveData와 Observer

hee09·2021년 12월 5일
3
post-thumbnail

LiveData와 Observer

뷰모델을 이용하면 화면과 데이터 모델 및 모델을 처리하는 로직을 분리해서 개발할 수 있습니다. 그런데 로직이 뷰모델에서 진행된다면 그 로직에 의해 발생한 데이터도 뷰모델이 가지고 있습니다. 이 데이터는 대부분 화면(액티비티, 프래그먼트)에 출력해야 하기에 액티비티나 프래그먼트로 전달해야 합니다. 뷰모델이 데이터를 전달할 때 LiveData를 사용합니다(LiveData 말고도 flow 등을 사용할 수 있습니다. flow와 관련해서는 Coroutine을 정리할 때 설명하겠습니다.).

LiveData 사용 이점

  1. 데이터가 변경될 시 자동으로 UI 변경
    LiveData는 Observer 패턴을 따릅니다. LiveData는 데이터가 변경되었을 시 Observer 객체에게 알립니다. Observer 객체에서는 UI를 업데이트 하는 코드를 넣습니다. 이러한 방식으로 데이터가 변경되면 Observer가 UI를 업데이트 해주기때문에 개발자는 직접 UI를 업데이트 하는 코드를 넣을 필요가 없습니다.

Observer 패턴에 대해 자세히 알아보려면 링크1, 링크2를 확인하시면 됩니다.

  1. 메모리의 결함이 없습니다.
    Observer는 LifeCycle 객체에 바인딩되기(묶이기) 때문에 연관된 생명주기가 destroy된다면 스스로 사라집니다.

  2. 액티비티가 stop되어도 충돌이 없습니다.
    백 스택에 있는 액티비티와 같이 Observer의 생명주기가 비활성 상태이면, Observer는 LiveData의 이벤트를 받지 않습니다.

  3. 수동적으로 생명주기를 다룰 필요가 없습니다.
    UI 요소들은 관련된 데이터만 관찰하고 관찰을 중지하거나 재개하지 않습니다. LiveData는 관찰하는 동안 관련된 생명주기 상태의 변화를 인식하기 때문에 모든 것(생명주기)을 자동으로 관리합니다.

  4. 항상 최신 데이터를 가집니다.
    만약 생명주기가 비활성화 되었다가 다시 활성화가 된다면 가장 최신의 데이터를 받습니다. 예를 들어, background에 있던 액티비티가 foreground로 돌아간 후 바로 최신의 데이터를 받습니다.

  5. 적절한 구성(환경..?)의 변화
    만약 액티비티나 프래그먼트가 휴대폰 가로-세로모드와 같이 구성의 변화로 인해 recreated된다면 가장 최신의 데이터를 즉시 받습니다.

  6. resource 공유
    싱글톤 패턴을 사용하여 LiveData를 확장해 앱에서 공유할 수 있도록 시스템 서비스를 래핑할 수 있습니다. LiveData 객체는 시스템 서비스에 한번 연결되면 리소스가 필요한 Observer는 LiveData 객체를 관찰할 수 있습니다.


LiveData 객체 사용 순서

  1. 데이터의 타입을 가지는 LiveData 객체를 생성합니다. 대부분 ViewModel 클래스 안에서 사용됩니다. View에서는 LiveData 객체를 생성하면 안됩니다.

  2. LiveData 객체가 가지고 있는 데이터가 변경될 때 발생하는 작업을 제어하는 onChanged() 메서드를 구현하는 Observer 객체를 만듭니다. 대부분 액티비티나 프래그먼트와 같은 UI controller안에 Observer 객체를 만듭니다.

  3. observe() 메서드를 사용하여 LiveData 객체에게 Observer를 붙입니다. observe() 메서드는 LifecycleOwner 객체를 가집니다. 이렇게 하면 Observer 객체가 LiveData 객체를 subscribe(구독)하여 LiveData의 변경 사항을 알 수 있습니다.

  • 관련된 LifecycleOwner 없이 Observer를 observerForever(Observer)를 사용하여 등록할 수 있습니다. 이렇게 되면 Observer는 언제나 활성상태이기에 변화를 계속해서 알아차립니다. 이러한 Observer는 removeObserver(Observer) 메서드를 사용하여 제거할 수 있습니다.

LiveData 만들기

LiveData는 위에서 언급했듯이 어떠한 데이터든 함께 사용할 수 있습니다. 아래 코드는 ViewModel안에서 String 타입의 LiveData를 선언하는 코드입니다.

class NameViewModel : ViewModel() {

    // String 타입의 LiveData 생성
    private val _currentName: MutableLiveData<String> = MutableLiveData("홍길동")
    val currentName: LiveData<String> = _currentName

    // Rest of the ViewModel...
}

LiveData는 추상 클래스이며 immutable입니다. 이를 상속받아서 mutable 하게 구현한 클래스가 MutableLiveData입니다.

_currentName 프로퍼티를 선언하고 타입은 MutableLiveData<String>로 지정해 String 타입임을 명시하였고 값은 "홍길동"으로 지정하였습니다. 그리고 이 값을 currentName이라는 LiveData에서 받고, View에서는 이 LiveData의 값을 사용하게 됩니다. 이와 같이 구성하는 이유는 MutableLiveData는 mutable하고, LiveData는 immutable하기 때문입니다. 즉, LiveData는 immutable하기에 thread-safe 하지만 값을 변경할 수 없어서 MutableLiveData를 통해 값을 변경하고 LiveData는 해당 값을 immutable하게 받는 것입니다.

thread-safe
멀티 스레드 프로그래밍에서 일반적으로 어떤 함수나 변수, 혹은 객체가 여러 스레드로부터 동시에 접근이 이루어져도 프로그램의 실행에 문제가 없음을 뜻한다. 보다 엄밀하게는 하나의 함수가 한 스레드로부터 호출되어 실행 중일 때, 다른 스레드가 그 함수를 호출하여 동시에 함께 실행되더라도 각 스레드에서의 함수의 수행 결과가 올바로 나오는 것이다.


LiveData 객체 관찰(Observer)

LiveData는 Observer 인터페이스를 사용하여 관찰합니다. Observer 인터페이스에는 유일한 메서드인 onChanged가 있습니다. 이 메서드는 LiveData의 값이 변경되었을 때 호출되는 콜백 메서드로 이 메서드 안에서 LiveData의 값을 받아 사용하면 됩니다.

대부분의 경우, 앱 컴포넌트의 onCreate() 메서드 안에서 LiveData에 Observer를 달아 관찰을 시작합니다. 그 이유는 아래와 같습니다.

  • 시스템이 액티비티 또는 프래그먼트의 onResume() 메서드에서 중복 호출을 하지 않도록 하기 위해서 입니다.

  • 액티비티나 프래그먼트가 활성화되는 즉시 표시할 데이터가 있는지 확인합니다. 앱 컴포넌트가 STARTED 상태에 있는 즉시 관찰 중인 LiveData 객체로부터 가장 최근 값을 수신합니다. 이것은 LiveData 객체가 이미 onCreate에서 설정된 경우에만 발생합니다.

일반적으로 LiveData는 데이터가 변경될 때 활성화된 Observer에게만 업데이트를 제공합니다. Observer가 비활성 상태에서 활성 상태로 변경될 때 또한 업데이트를 수신합니다. 게다가, 만약 두 번째로 비활성 상태에서 활성 상태로로 변경되는 경우 마지막으로 활성된 이후 값이 변경된 경우에만 업데이트를 수신합니다.

class MainActivity : AppCompatActivity() {
	
    lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
	    // 데이터바인딩
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
	    // ViewModel 객체 생성
        val myViewModel: MyViewModel = ViewModelProvider(this).get(MyViewModel::class.java)

        // Obserber 객체 생성
        // 인터페이스에 선언된 함수가 하나이기에 SAM에 해당하여 람다로 표현하면 깔끔합니다.
        val nameObserver = object : Observer<String> {
            override fun onChanged(t: String?) {
                binding.nameView.text = t
            }
        }

        // 위의 코드와 같은 코드
        val nameObserver2 = Observer<String> {
            binding.nameView.text = it
        }

        // LifecycleOwner(이 코드에서는 액티비티에 해당)와 Observer객체를 넣어 LiveData 관찰
        myViewModel.currentName.observe(this, nameObserver)
        
        // Observer + observe 코드를 한꺼번에 선언하는 코드
        myViewModel.currentName.observe(this) {
        	binding.nameView.text = it
        }
    }
}

observe() 메서드에 Observer 객체를 전달하면 즉시 Observer 객체안에 구현한 onChanged()가 호출되어 currentName에 저장된 가장 최근 값을 제공받습니다. 위 코드는 그 값을 받아 텍스트뷰에 셋팅하는 코드입니다.


LiveData 객체 업데이트

LiveData 객체를 업데이트 하기 위해서 MutableLiveData의 setValue() 또는 postValue() 메서드를 사용합니다. 이 두 메서드 모두 뷰모델에서 결과 데이터를 LiveData에 담는 역할을 합니다. 둘의 차이가 있다면 PostValue() 메서드는 백그라운드 스레드에 의해 동작하고 setValue() 메서드는 메인 스레드에 의해 동작합니다.

ViewModel 코드

class MyViewModel: ViewModel() {
    private val _currentName: MutableLiveData<String> = MutableLiveData("홍길동")
    val currentName: LiveData<String> = _curentName

    // 액티비티에서 호출할 메서드 
    fun postNameValue(name: String) {
        // setValue()를 사용하여 값 셋팅
        _currentName.value = name
    }
}

액티비티 코드

// 버튼과 연결된 메서드
fun nameBtnClick() {
	myViewModel.postNameValue("Hong Gil Dong")
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    
    nameBtn.setOnClickListener {
    	nameBtnClick()
    }
    
    // Observer를 연결하여 LiveData의 값을 관찰
    myViewModel.currentName.observe(this) {
        binding.nameView.text = it
    })
}

위 코드는 ViewModel 클래스에 액티비티에서 호출할 메서드를 작성하고 setValue를 통해 MutableLiveData의 값을 변경합니다. 액티비티에서는 버튼을 클릭하면 ViewModel에 선언된 메서드를 호출하며 Observer를 통해 LiveData의 값의 변경을 관찰하고 UI에 그 값을 출력합니다. 이 예제는 간단한 코드를 위해 버튼을 클릭하지만 네트워크의 요청 - 응답 또는 데이터베이스 로드 완료에 대한 응답을 포함해 LiveData의 업데이트를 수행할 수 있습니다.


커스텀 LiveData

위에서 계속 MutableLiveData를 사용하였는데 언급하였듯이 MutableLiveData는 LiveData를 상속받아 구현한 클래스입니다. MutableLiveData를 사용하지 않고 LiveData를 상속받아 직접 커스텀 라이브데이터 클래스를 만들어 이용할 수 있습니다.

// LiveData 상속받는 클래스 작성
class MyLiveData: LiveData<String>() {
    fun setData(data: String) {
        // 옵저버에 데이터를 전달하기 위해 postValue 또는 setValue 호출
        postValue(data)
    }

    // LiveData 객체에 활성화된 Observer가 있을 때 호출
    override fun onActive() {
        super.onActive()
    }

    // LiveData 객체에 활성화된 Observer가 없을 때 호출
    override fun onInactive() {
        super.onInactive()
    }
}
  • onActive()
    LiveData 객체가 활성화 된 Observer를 가지고 있을 때 호출됩니다.

  • onInactive()
    LiveData 객체가 활성화 된 Observer를 가지고 있지 않을 때 호출됩니다.

  • setValue() or postValue()
    LiveData 객체의 값을 업데이트하고 활성화 된 Observer에게 변화에 대해 공지하는 메서드입니다.


정리

  • LiveData는 데이터 홀더이며 데이터를 관찰할(observable)할 수 있게 해줍니다. 기본적으로 관찰 가능한 객체는 자신의 데이터가 변경될 때 다른 객체에게 알려줄 수 있는 기능을 갖고 있습니다. 따라서 ViewModel에서 LiveData를 통해 데이터를 가지고 있고, View에서 해당 LiveData를 관찰하고 있으면 데이터가 변경될 때 View는 이를 통보받을 수 있는 것입니다.

  • LiveData는 자신을 관찰하는 옵저버(observer)의 생명주기 상태를 알고 있습니다. 따라서 만일 액티비티에서 LiveData를 관찰한다면 해당 액티비티가 일시 중지 상태(onPause)이면 LiveData는 옵저버에게 통지(이벤트 전송)을 중단합니다. 그리고 액티비티가 다시 실행을 재개하면(onStart-onResume) 액티비티가 최신 데이터의 값을 받도록 LiveData는 옵저버에게 이벤트를 전송할 것입니다.

  • 위와 같은 이유로 LiveData를 안드로이드에서는 Lifecycle-aware components로 분류하고 있습니다.


참조
깡쌤의 안드로이드 프로그래밍
안드로이드 developer - LiveData

틀린 부분 댓글로 남겨주시면 수정하겠습니다..!!

profile
되새기기 위해 기록

0개의 댓글