본문 바로가기
Development/Android

Android - Deep dive into LiveData - 2. setValue vs postValue

by du.it.ddu 2023. 1. 21.
반응형

LiveData를 사용할 때, 데이터의 변경은 어떻게 할 수 있을까?
제목에도 나와있듯이, setValue와 postValue가 있다.

왜 두 가지 방법이 있을까?
무슨 차이가 있는지 알아보자.


 

public abstract class LiveData<T> {   
   ...
   
   /**
     * Sets the value. If there are active observers, the value will be dispatched to them.
     * <p>
     * This method must be called from the main thread. If you need set a value from a background
     * thread, you can use {@link #postValue(Object)}
     *
     * @param value The new value
     */
    @MainThread
    protected void setValue(T value) {
        assertMainThread("setValue");
        mVersion++;
        mData = value;
        dispatchingValue(null);
    }
    ...
}

setValue 함수의 구현이다.
LiveData가 가진 값을 바로 메인 쓰레드에서 변경하며 활성화된 관찰자에게 알림을 보낸다.
파라미터는 변경할 새로운 값을 받는다.

주석을 보면, 값을 백그라운드에서 변경하고 싶다면 postValue를 사용하라고 한다.
postValue의 구현을 알아보자.


public abstract class LiveData<T> {   
   ...
   
    /**
     * Posts a task to a main thread to set the given value. So if you have a following code
     * executed in the main thread:
     * <pre class="prettyprint">
     * liveData.postValue("a");
     * liveData.setValue("b");
     * </pre>
     * The value "b" would be set at first and later the main thread would override it with
     * the value "a".
     * <p>
     * If you called this method multiple times before a main thread executed a posted task, only
     * the last value would be dispatched.
     *
     * @param value The new value
     */
    protected void postValue(T value) {
        boolean postTask;
        synchronized (mDataLock) {
            postTask = mPendingData == NOT_SET;
            mPendingData = value;
        }
        if (!postTask) {
            return;
        }
        ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
    }
    ...
}

주석도 많고 코드도 뭔가 많다.
postValue는 값의 변경을 post 한다. 즉, 변경을 요청하고, 값의 변경이 바로 이루어지지 않는다.
Handler를 사용해본 경험이 있다면 이해하기 쉬울 것이다.
값의 변경을 메인쓰레드에 post하여 요청하면, 메인쓰레드는 다음 메시지 처리 때 이를 수행한다.

그럼 setValue와 postValue는 어떤 차이가 있을까?


아래 코드를 보자.

class MyViewModel : ViewModel() {
    private val _myLiveData: MutableLiveData<String> = MutableLiveData()
    
    private fun changeMyLiveData() {
        viewModelScope.launch(Dispatchers.IO) {
            _myLiveData.value = "somthing" // NO
            _myLiveData.postValue("something") // OK
        }
    }
}

ViewModelScope에서 코루틴을 IO 쓰레드에서 실행했다.
이 때, setValue를 수행하면 메인쓰레드에서 실행하려고 시도하지만, 메인쓰레드가 아니므로 Exception이 발생한다.
반면에 postValue는 메인쓰레드에 post할 뿐이므로 문제가 없다.
즉, postValue는 함수가 호출되는 쓰레드에 무관하게 사용할 수 있다.

여기까지 하면 마치 postValue가 가장 유용할 것 처럼 보인다.
다만, 값의 동기화 측면에서 postValue는 문제가 있다.


postValue의 값 동기화 문제

class MyViewModel : ViewModel() {
    private val _myLiveData: MutableLiveData<String> = MutableLiveData()
    
    private fun changeMyLiveData() {
        viewModelScope.launch(Dispatchers.IO) {
            _myLiveData.postValue("abc")
            val currentValue = _myLiveData.value
            
            Log.d(TAG, "currentValue = $currentValue")
        }
    }
}

위와 같은 코드가 있을 때, Log에는 어떤 값이 남을까?
"abc"가 남기를 희망하지만, 실제로는 그 전의 값이 남겨진다.
왜냐하면 메인쓰레드에 post된 요청이 수행되지 않았기 때문이다.


class MyViewModel : ViewModel() {
    private val _myLiveData: MutableLiveData<String> = MutableLiveData()
    
    private fun changeMyLiveData() {
        viewModelScope.launch(Dispatchers.Main) {
            _myLiveData.value = "abc"
            val currentValue = _myLiveData.value
            
            Log.d(TAG, "currentValue = $currentValue")
        }
    }
}

반면, 메인쓰레드에서 setValue를 사용했을때는 어떨까?
메인쓰레드에 바로 값이 반영되므로, 기대했던 대로 Log에는 "abc" 가 남겨진다.

이는 ViewModel 내에서 뿐만 아니라, 값을 관찰하는 Activity, Fragment에서 값을 참조할때도 문제가 발생할 수 있다.
따라서 값의 동기화가 필요한 상황이라면 메인쓰레드에서 setValue를 사용하는 것이 안전하다.


여기까지 LiveData의 값을 변경하는 과정을 알아보았다.
setValue, postValue 두 가지로 나누어진 것엔 이유가 있고, 용도가 있다.
따라서 본인의 로직에 맞는 것을 사용해야 할 것이다.

여기서 궁금한 것이 있다.
postValue를 사용할 때, 다른 LiveData의 값이 변경을 체크하면 문제가 있다.
그런데 다른 LiveData의 값 변경을 수신할 필요가 있고, setValue를 쓰지 못하거나(혹은 싫거나) 하는 상황은 어떻게 해결할까?

다음 포스팅에서 이를 다루어본다.

반응형