Android Jetpack 은 개발자가 고품질 앱을 쉽게 개발할 수 있도록 지원하는 라이브러리, 도구, 가이드의 모음 입니다. Jetpack 은 androidx.* 패키지 라이브러리로 지원되며, 크게 4가지 구성요소(기초, 아키텍처, 동작, UI)를 가집니다.

그 중 아키텍처 구성요소(AAC, Android Architecture Component)는 Data binding, Lifecycle, ViewModel, Room, Work manager 등을 포함하고 있습니다. 오늘 얘기할 주제인 LiveData 도 여기에 포함됩니다.

https://developer.android.com/jetpack?hl=ko

MVVM 패턴의 관점에서 보자면… ViewModel 은 UI 와 UI controller 간의 역할 분담을 기반으로 Activity/Fragment 의 생명 주기를 자동으로 모니터링 할 수 있도록 해줍니다. (ViewModel 예제 링크)

반면에 LiveData 는 Activity/Fragment 의 UI 구성요소들과 UI controller 가 가진 개별 data 간의 동기화를 담당하는 Data holder 클래스입니다. 또한 LiveData 는 ViewModel 처럼 Activity/Fragment 의 생명 주기를 모니터링해서 stopped/destroy 상태에서 불필요한 UI 구성요소 access 를 방지하는 기능이 들어가 있습니다. 따라서 정지된 Activity 로 인한 crash 를 방지해줍니다.
(실제 LiveData는 Activity 가 STARTED/RESUMED -> STOPPED 상태까지 활성화 된다고 보면 됩니다.)

ViewModel 이 class 단위에서 View : View model 의 분리를 위한 interface 역할을 한다면, LiveData 는 View : View model 이 가진 개별 element 간의 결합을 위한 interface 역할을 한다고 볼 수 있습니다.

.

.

LiveData 예제

LiveData 구현 예제는 GitHub – LiveDataExample 에서 보실 수 있습니다.

ViewModel 예제와 마찬가지로 아래처럼 버튼을 클릭하면 클릭 횟수를 1씩 증가해서 출력하도록 되어 있습니다.

app/src/main/java/com/example/livedataexample/ 경로에는 2개의 코틀린 파일이 있습니다. Activity – ModelView 를 구현해서 하나의 화면을 만듭니다.

  • MainActivity.kt
    • ViewModel 을 생성하고 연결
    • LiveData 에 Observer 연결 (LiveData 가 변경되면 호출되는 callback)
    • ViewModel 초기화
    • 버튼 클릭 리스너 설정 (ViewModel 에 이벤트 전달)
class MainActivity : AppCompatActivity() {
    lateinit var viewModel : MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // create basic view model
        viewModel = ViewModelProvider(this).get(MainViewModel::class.java)

        // observe live data
        viewModel.countText.observe(this, Observer {
            // it 는 LiveData 로 선언된 countText 가 변경되었을 때 전달되는 값(String 형)
            text_desc1.text = it
        })

        // initialize view model
        viewModel.init()

        // update UI
        button_class.setOnClickListener {
            viewModel.clickButton()
        }
    }
}

.

MainViewModel.kt

  • MutableLiveData 생성
  • init() 초기화 함수
  • clickButton() 클릭 이벤트 처리 함수
  • ViewModel – onCleared() 구현
class MainViewModel : ViewModel() {
    val TAG = "MainViewModel"
    private var count = 0
    val countText : MutableLiveData<String> = MutableLiveData()

    fun init() {
        countText.value = "click count : $count"
    }

    fun clickButton() {
        countText.value = "click count : ${++count}"
    }

    override fun onCleared() {
        Log.d(TAG, "## MainViewModel - onCleared() called!!")
        Log.d(TAG, "## count = $count")
        super.onCleared()
    }
}

.

MainViewModel.kt 에서 String 타입의 MutableLiveData 를 정의한 코드부터 보겠습니다.

val countText : MutableLiveData = MutableLiveData()

LiveData 타입은 객체 외부에서 내부의 데이터를 수정할 수 있는 방법을 제공하지 않습니다. 반면 MutableLiveData는 setValue(), postValue() 와 같은 데이터 변경용 함수를 제공하기 때문에 객체 외부에서 데이터 변경이 가능합니다.

그러면 LiveData 타입은 언제 사용하느냐? MutableLiveData 처럼 변경 가능한 변수를 private 으로 설정해서 ModelView에서 내부적으로 사용하고, LiveData 변수를 public으로 선언해서 MutableLiveData 변수를 대입합니다. 그럼 ModelView 외부에서는 LiveData 만 사용하게 되므로 외부에서 값을 변경할 수 없습니다. 보안이 강화되는 효과를 볼 수 있습니다.

이제 Activity 에서 LiveData 변수에 접근해서 Observer를 설정하면, LiveData 의 값이 변결될 때 Activity의 생명 주기에 맞춰 자동으로 값을 가져갑니다. (callback 역할을 하는 Observer 가 자동 호출됩니다.) MainActivity.kt 에 있는 아래 코드가 이 역할을 합니다.

    override fun onCreate(savedInstanceState: Bundle?) {
        ......
        // observe live data
        viewModel.countText.observe(this, Observer {
            // it 는 LiveData 로 선언된 countText 가 변경되었을 때 전달되는 값(String 형)
            text_desc1.text = it
        })
    }

.

LiveData 가 변경되면 UI 가 자동 변경되는 코드는 넣었으니, 이제는 버튼을 클릭하면 ModelView 내부에서 LiveData 값이 변경되도록 만든 코드를 보겠습니다.

class MainActivity : AppCompatActivity() {
    ......
    override fun onCreate(savedInstanceState: Bundle?) {
        ......
        // update UI
        button_class.setOnClickListener {
            viewModel.clickButton()
        }
    }
}

class MainViewModel : ViewModel() {
    ......
    fun clickButton() {
        countText.value = "click count : ${++count}"
    }
}

Activity 코드를 보면 버튼에 setOnClickListener() 를 이용해 리스너를 설정했습니다. 버튼을 클릭하면 ViewModel 의 clickButton() 이 호출됩니다.

clickButton() 함수는 MutableLiveData 인 countText 의 String 값을 변경합니다. MutableLiveData 는 값을 변경할 때 setValue(), postValue() 를 사용할 수 있습니다. 각각의 함수는 UI thread, BG thread 에서 사용 가능한 함수입니다. 여기서는 setValue() 를 사용해서 값을 변경했습니다.

이제 버튼을 클릭하면 ViewModel 의 LiveData 값이 변경되고, LiveData 의 값이 변경되었으므로 자동으로 Activity의 Observer 코드가 호출됩니다.

.

.

LiveData 활용

예제에서 LiveData 를 구현할 때 MutableLiveData 를 사용한 것 처럼 LiveData 를 다양한 로직에서 사용하기 편하도록 LiveData를 변환하는 방법들이 있습니다. Transformations 클래스를 사용하면 map(), switchMap() 함수를 통해 LiveData 병합, 전환을 할 수 있습니다. 또한 LiveData 자체적으로 변환을 구현하기 위해 MediatorLiveData 클래스를 사용할 수도 있습니다. 이와 관련된 상세 내용은 아래 링크를 참고하세요.

LiveData 는 Observer 패턴으로 구현되어 있습니다. LiveData 뿐만 아니라 Lifecycle, ViewModel 과 같은 Android Architecture Component 의 구성요소들이 Observer 패턴을 적극 사용하고 있습니다. 이런 점에 착안해서 LiveData를 Observer 패턴을 적용할 수 있는 모듈간의 인터페이스로도 활용할 수 있습니다. 이 경우에는 LifecycleOwner 개체(예제에서는 observe() 호출할 때 activity를 사용)없이 사용가능한 observeForever(Observer), removeObserver(Observer) 를 이용할 수 있습니다.

어떤 데이터는 여러 Activity/Fragment 에서 공유되어 사용되길 원할 수도 있을것입니다. 이런 경우는 LiveData 클래스를 상속받아 Singleton으로 구현하는 방법을 사용할 수 있습니다.


참고