
안드로이드 개발을 하는 우리는, 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