안드로이드 개발을 하는 우리는, LiveData
라는 녀석과 친숙할 것이다. ViewModel (AAC) 내의 데이터들을 LiveData 로 관리함으로써 View 의 데이터를 항상 최신으로 유지할 수 있는 것은 누구나 알고 있는 사실이다. 그러나 딱 이정도만 알고있다면, 용법에 어긋나게 잘못 사용할 가능성이 있다. 따라서 이번 포스팅에선 LiveData
의 올바른 사용을 위해, LiveData
의 데이터 처리 방법에 대해 알아보고자 한다.
LiveData 는 Observer 패턴을 활용하여, Lifecycle 에 따르며 데이터를 관리해주는 녀석이다. 이름 그대로 '실시간' 그 자체의 데이터, 즉 항상 최신 데이터를 보증한다는 특징이 있다.
옵저버 패턴이 뭔지 모른다면? 필자가 작성한 해당 포스팅을 참고해보자
Observer 패턴을 사용하기 때문에, 데이터의 변화를 실시간으로 구독자에게 통지할 수 있다.
Activity / Fragment 의 라이프사이클을 따라 동작하기 때문에, 메모리 릭이 발생하지 않는다.
→ 연결된 컴포넌트의 수명주기가 끝나면 (죽으면) LiveData 도 자동으로 삭제됨
항상 최신 데이터를 유지하기 때문에 기기 회전이 일어나도 View 의 데이터가 날아가지 않는다
→ ViewModel (AAC) 과 함께 사용했을 때의 이야기
해당 예제 코드는 타 블로그 에서 가져왔음을 밝힙니다.
class LoadDetailViewModel : ViewModel() {
private val _liveData = MutableLiveData<String>()
val liveData: LiveData<String>
get() = _liveData
fun loadData() = viewModelScope.launch {
_liveData.value = "value"
_liveData.postValue("value")
}
}
// SAM (Single Abstract Method) 활용하여 Observer 구현
loadDetailViewModel.liveData.observe(this) {
// 변경된 데이터를 통해 View 업데이트 처리 동작
}
이 예제에서의 ViewModel
내의 LiveData 와 관련된 코드를 하나씩 차근차근 살펴보자.
코틀린을 사용하는 사람들이라면 쉽게 이해할 수 있다. 코틀린은 MutableList
등 Mutable
이라는 키워드를 상당히 좋아하는데, 이는 변경될 수 있는 데이터와 변경될 수 없는 데이터를 명확히 구분하여, 개발자의 실수로 머티 쓰레딩 환경 등에서 변경되어선 안 될 데이터가 의도와 다르게 변경되는 등의 상황을 방지하기 위함이다. (불변성 강조)
LiveData 도 마찬가지로, MutableLiveData
는 GET / SET 이 모두 가능하며, LiveData
는 GET 만 가능하다.
🤷🏻♂️ 엥? LiveData 는 어차피 변경되어야 하는 데이터 아닌가? 왜 구분함?
위 예제 코드를 보면,
MutableLiveData
는private
키워드를 통해 은닉화되어있고 값의 변경이 불가능한LiveData
만 외부에 공개되어있는 것을 확인할 수 있다. 왜 이렇게 구현한 것일까?
정답은 View 와 ViewModel 의 역할 분리를 명확히 하기 위함이다. ViewModel 내에서는 데이터를 지지고 볶을 수 있지만, View 는 이렇게 계속하여 바뀌는 데이터를 읽을수만 있도록 허용하는 것이다. View 는 비즈니스 로직보다는 단순히 사용자에게 데이터를 보여주는 역할만 해야하기 때문이다.
🤷🏻♂️ View 에서 값을 변경해야 하는 경우는 어쩌라고?
이런 경우에는 당연히 허용한다.. 은닉화를 너무 강제하는 것은 아니다. 예를 들어
EditText
나SeekBar
의 데이터는 순전히 사용자가 변경하는 것이므로, 이러한 상황 (양방향 데이터 바인딩) 에선MutableLiveData
를 외부에 공개해도 된다. 상황에 맞게 적절히 구현하면 된다.
LiveData
의 데이터는 setValue()
와 postValue()
를 통해 변경할 수 있다.
setValue()
: MainThread 가 보장될 경우 활용 (MainThread 에서의 값 업데이트)postValue()
: MainThread 가 아닌 IO 스케줄러 활용 시 활용 (MainThread 로 값 전달)이들의 내부 코드를 살펴보자. 우선 setValue()
내부 코드이다. 명확하게 @MainThread
어노테이션이 붙어있고, assert
까지 붙어있어 MainThread 가 아닌 곳에서 이를 활용할 경우 런타임 시 오류가 발생할 것이다.
@MainThread
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
다음은 postValue()
이다. 이 메소드 내에서는 값 처리를 하지 않고, 마지막에 postToMainThread()
를 호출함으로써 MainThread 로 값을 전달하는 것을 확인할 수 있다.
protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
postTask = mPendingData == NOT_SET;
mPendingData = value;
}
if (!postTask) {
return;
}
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
전달하는 Runnable
객체의 내부 코드도 살펴보자. mPendingData
가 데이터를 갖고 있고, run()
이 호출될 때 비로소 setValue()
를 부름으로써 LiveData 의 값을 변경하는 것을 알 수 있다.
private final Runnable mPostValueRunnable = new Runnable() {
@SuppressWarnings("unchecked")
@Override
public void run() {
Object newValue;
synchronized (mDataLock) {
newValue = mPendingData;
mPendingData = NOT_SET;
}
setValue((T) newValue);
}
};
이렇듯 내부코드를 파헤쳐 본 결과, 결국 LiveData 는 항상 MainThread 로 값을 처리함을 알 수 있다.
따라서 아래와 같은 상황에서 LiveData 를 사용하는 것은 용법이 잘못된 것이다.
- ViewModel 보다 더 하위 객체인 Repository 와 같은 내부에서 LiveData 를 통해 값을 가져오는 경우
- UI (View) 업데이트가 없는 코드에서 LiveData 를 활용하는 경우
따라서 데이터의 처리가 IO 스케줄러 상에서 발생해야 하는 경우, LiveData 를 사용하는 것은 올바르지 않은 사용이다. 따라서 이런 경우 Thread 나 RxJava, Coroutines 등을 활용하여 처리해야 한다.
위 내용에서 setValue()
의 내부 코드를 보았는데, 이를 보면 알 수 있듯 결국 내부에는 단순 변수로 구현이 되어있기 때문에 항상 마지막으로 변경된 데이터를 갖고 있을 수 밖에 없다.
fun loadData() = viewModelScope.launch {
_liveData.value = "value1"
_liveData.value = "value2"
}
fun loadData() = viewModelScope.launch {
_liveData.postValue("value1")
_liveData.postValue("value2")
}
따라서 이런식으로 데이터를 날려도, 항상 value2
가 담기게 되는 것이다.
때문에 만약 연속적인 값 변경 처리를 해야하고 각각의 값이 유의미한 경우 (유실되어선 안될 경우), LiveData 를 따로 두는 편이 좋다.
이번 포스팅에선 LiveData
의 사용 목적과 특성에 대해 알아보았다. 무지성으로 '음 MVVM 패턴 구현을 위해 AAC 의 ViewModel 을 사용해야 하고 이 안에서는 LiveData 로 데이터를 관리하면 되는군' 에서 끝나는 것이 아닌, 존재 의의와 용법에 맞는 올바른 LiveData 사용을 위해 반드시 알아야 할 내용이다.
https://thdev.tech/android/2021/02/01/LiveData-Intro/
https://developer.android.com/topic/libraries/architecture/livedata