[Android] LiveData 알고 사용하기

haero_kim·2021년 10월 3일
5

Android 와 친해지기

목록 보기
17/22
post-thumbnail

안드로이드 개발을 하는 우리는, LiveData 라는 녀석과 친숙할 것이다. ViewModel (AAC) 내의 데이터들을 LiveData 로 관리함으로써 View 의 데이터를 항상 최신으로 유지할 수 있는 것은 누구나 알고 있는 사실이다. 그러나 딱 이정도만 알고있다면, 용법에 어긋나게 잘못 사용할 가능성이 있다. 따라서 이번 포스팅에선 LiveData 의 올바른 사용을 위해, LiveData 의 데이터 처리 방법에 대해 알아보고자 한다.


LiveData 개념

LiveData 는 Observer 패턴을 활용하여, Lifecycle 에 따르며 데이터를 관리해주는 녀석이다. 이름 그대로 '실시간' 그 자체의 데이터, 즉 항상 최신 데이터를 보증한다는 특징이 있다.

옵저버 패턴이 뭔지 모른다면? 필자가 작성한 해당 포스팅을 참고해보자

LiveData 장점

  • Observer 패턴을 사용하기 때문에, 데이터의 변화를 실시간으로 구독자에게 통지할 수 있다.

  • Activity / Fragment 의 라이프사이클을 따라 동작하기 때문에, 메모리 릭이 발생하지 않는다.
    → 연결된 컴포넌트의 수명주기가 끝나면 (죽으면) LiveData 도 자동으로 삭제

  • 항상 최신 데이터를 유지하기 때문에 기기 회전이 일어나도 View 의 데이터가 날아가지 않는다
    → ViewModel (AAC) 과 함께 사용했을 때의 이야기


LiveData 사용법

해당 예제 코드는 타 블로그 에서 가져왔음을 밝힙니다.

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 와 관련된 코드를 하나씩 차근차근 살펴보자.


### `MutableLiveData vs LiveData`

코틀린을 사용하는 사람들이라면 쉽게 이해할 수 있다. 코틀린은 MutableListMutable 이라는 키워드를 상당히 좋아하는데, 이는 변경될 수 있는 데이터와 변경될 수 없는 데이터를 명확히 구분하여, 개발자의 실수로 머티 쓰레딩 환경 등에서 변경되어선 안 될 데이터가 의도와 다르게 변경되는 등의 상황을 방지하기 위함이다. (불변성 강조)

LiveData 도 마찬가지로, MutableLiveData 는 GET / SET 이 모두 가능하며, LiveData 는 GET 만 가능하다.

🤷🏻‍♂️ 엥? LiveData 는 어차피 변경되어야 하는 데이터 아닌가? 왜 구분함?

위 예제 코드를 보면, MutableLiveDataprivate 키워드를 통해 은닉화되어있고 값의 변경이 불가능한 LiveData 만 외부에 공개되어있는 것을 확인할 수 있다. 이렇게 구현한 것일까?

정답은 View 와 ViewModel 의 역할 분리를 명확히 하기 위함이다. ViewModel 내에서는 데이터를 지지고 볶을 수 있지만, View 는 이렇게 계속하여 바뀌는 데이터를 읽을수만 있도록 허용하는 것이다. View 는 비즈니스 로직보다는 단순히 사용자에게 데이터를 보여주는 역할만 해야하기 때문이다.

🤷🏻‍♂️ View 에서 값을 변경해야 하는 경우는 어쩌라고?

이런 경우에는 당연히 허용한다.. 은닉화를 너무 강제하는 것은 아니다. 예를 들어 EditTextSeekBar 의 데이터는 순전히 사용자가 변경하는 것이므로, 이러한 상황 (양방향 데이터 바인딩) 에선 MutableLiveData 를 외부에 공개해도 된다. 상황에 맞게 적절히 구현하면 된다.


LiveData 는 항상 UI 업데이트를 목적으로

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 등을 활용하여 처리해야 한다.


LiveData 는 항상 최신 데이터를 갖고 있다

위 내용에서 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

profile
어려울수록 기본에 미치고 열광하라

0개의 댓글