#1 개요
#1-1 LiveData
LiveData. 문서적인 정의는 Data의 변경을 관찰할 수 있는 데이터 홀더 클래스고, 말 그대로 해석하면 살아있는(live) 데이터(data)다. Activity, Fragment, Service에 있는 Data, 예를 들어 어떤 변수 A가 있고 그 A를 어떤 TextView에 표시해놓은 상황을 생각해 보자. A의 값이 변경되면, TextView에 변경된 A값을 다시 할당해야한다. LiveData는 그 귀찮은 할당 과정을 프로그래머가 생략할 수 있게 만들어준다. LiveData는 변경된 A의 값을 화면(View)에 암묵적으로 업데이트한다. 초기 설정만 마쳐놓으면 그 이후로는 'binding.text = getA().toString()'와 같은 코드를 자동으로 수행한다는 얘기다. 알아서 변경된 자신의 값을 View에 갱신한다니, 말 그대로 데이터가 살아있다고 표현할 만하다.
#1-2 LiveData를 접목시켜볼 앱
이제, 위 게시글의 '#3 완성된 앱'이 LiveData를 사용하게 수정해본다. 아래에 있는 모든 코드는 이 앱의 코드를 기반으로 수정한 것이다.
#2 LiveData 사용하기
#2-1 build.gradle.kts (Module :app) 수정
plugins {
...
}
android {
...
}
dependencies {
...
val lifecycle_version = "2.5.1"
implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version") // LiveData
}
먼저, LiveData 추상클래스를 불러오기 위한 라이브러리를 추가한다. 모듈 수준 gradle에 위와 같은 구문을 추가한다. 해당 구문은 여기에서 가져온 구문이다.
#2-2 MainActivityViewModel.kt 수정
...
import androidx.lifecycle.MutableLiveData
...
class MainActivityViewModel : ViewModel() {
private var count : MutableLiveData<Int> = MutableLiveData(0)
fun getCurrentCount(): MutableLiveData<Int> {
return count
}
fun updateCount(){
count.value = (count.value)?.plus(1)
}
}
변수 count를 Int형이 아니라, MutableLiveData<Int>형으로 변경한다. 클래스 MutableLiveData는 추상클래스인 LiveData의 subclass다. MutableMap과 Map의 관계와 같다. 추상클래스는 객체를 생성할 수 없기에 MutableLiveData의 객체로 만든 것이다.
getCurrentCount()은 반환형을 수정한다. getUpdatedCount()는 updateCount()로 이름을 바꾸고 내용도 변경한다. count.value += 1과 같이 짧게 적을 수 없는 이유는 count.value가 null값일 가능성이 있기 때문이다. 따라서 null-safe 연산을 수행하는 plus() 함수를 사용한다. count.value가 null이라면 .plus(1)은 호출되지 않을 것이다.
MainActivity.kt를 수정하기 전에 LiveData가 어떻게 '살아있을(Live) 수 있는지' 알아본다.
#2-3 LiveData.observe() 살펴보기
...
public abstract class LiveData<T> {
...
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
assertMainThread("observe");
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
}
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
owner.getLifecycle().addObserver(wrapper);
}
...
}
당연히, LiveData가 정말로 살아있을리는 없다. 그렇다면 LiveData는 어떻게 변경된 자신의 값을 View에 갱신하는가? 바로 LiveData.observe() 메소드에 의해서다. 'observe'라는 단어 정의 그대로, LiveData의 변화를 '관찰'하는 메소드다. 이 메소드는 첫번째 인자로 LifecycleOwner, 두번째 인자로 Observer를 받는다.
LifecycleOwner 인터페이스는 단일 메소드 인터페이스다. 이는 Activity, Fragment, Service와 같이 생명주기(Lifecycle)를 소유한(Owner) 클래스 전부가 구현(implement)하고 있는 인터페이스다. 이 인터페이스의 단일 메소드는 LifecycleOwner.getLifecycle(): LifeCycle로 생명주기를 반환한다. 이 인자의 존재 이유는, LivaData의 메모리 누수 방지를 위함이다. LiveData의 목적은 변경된 자신의 값을 View에 갱신하는 것이다. 따라서, View에 LiveData가 노출되는 상태가 아니라면, 굳이 갱신을 하며 자원낭비를 할 필요가 없다. 마치 숨겨진 TextView에 값을 표시하는 것과 같은 행위기 때문이다. 그리고 View에 LiveData가 노출되고있는지 아닌지는 생명주기를 참조(Lifecycle.getCurrentState())해 알 수 있다.
두번째 인자인 Observer 인터페이스 또한 단일 메소드 인터페이스다. 그 단일 메소드는 Observer<T>.onChanged(value: T)로, LiveData 객체의 데이터 변경 시 발생하는 작업을 정의한다.
이 LiveData.observe()를 MainActivity.kt에서 구현해보겠다.
#2-4 MainActivity.kt 수정
...
import androidx.lifecycle.Observer
...
class MainActivity : AppCompatActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
...
val countObserver = object : Observer<Int> {
override fun onChanged(newValue: Int) {
binding.countText.text = newValue.toString()
}
}
viewModel.getCurrentCount().observe(this, countObserver)
binding.countButton.setOnClickListener {
viewModel.updateCount()
}
}
}
LiveData.observe()의 첫번째 인자로 LifecycleOwner를 이미 구현하고 있는 MainActivity 자체(this)를 넣는다. 두번째 인자로는 Observer의 구현 클래스를 넣으면 된다. getUpdatedCount() 메소드는 updateCount() 메소드로 이름과 기능을 바꿨으므로, binding.countButton의 setOnClickListener { ... } 블록의 내용도 그에 맞춰 수정한다.
Observer 구현 클래스 부분의 가독성이 약간 거슬리므로,
/*
val countObserver = object : Observer<Int> {
override fun onChanged(newValue: Int) {
binding.countText.text = newValue.toString()
}
}
viewModel.getCurrentCount().observe(this, countObserver)
*/
viewModel.getCurrentCount().observe(this, Observer {
binding.countText.text = it.toString()
})
와 같이 익명 클래스(클래스 정의와 객체화를 동시에 하는 클래스)를 사용해 코드를 줄인다.
이제 count의 값이 변경되어도 그 값을 내가 View에 명시적으로 재할당할 필요가 없다. LiveData.observe()가 암시적으로 재할당해줄 것이기 때문이다. 프로젝트의 크기가 작은 현재 게시글의 앱에서는 크게 와닿지 않지만, 프로젝트의 크기가 커질수록 LiveData의 이점이 체감될 것이다.
#3 요약
LiveData는 onChangeListener()의 세련된 사용이다.
#4 완성된 앱
'깨알 개념 > Android' 카테고리의 다른 글
[Android] LiveData - 암시적으로 '관찰'하기 (0) | 2024.01.19 |
---|---|
[Android] ViewModel - View에 객체(ViewModel) 전달 (0) | 2024.01.18 |
[Android] ViewModel - 뷰 모델에 인자(Argument) 전달 (ViewModelFactory) (0) | 2024.01.15 |
[Android] ViewModel - 기초 (0) | 2024.01.13 |
[Android] Data Binding - View에 객체 전달 (0) | 2024.01.12 |