본문 바로가기

안드로이드

[Android] SingleLiveEvent + 기타: setValue() vs postValue()

SingleLiveEvent

SingleLiveEvent는 한 번에 하나의 관찰자만 관찰하는 MutableLiveData의 하위 클래스로 뷰의 생명주기를 인식한다. 이를 사용하는 이유는 액티비티의 화면 회전과 같은 변화(Configuration Change)가 발생하여 onStart/onResume이 다시 호출되는 일이 발생하면 LiveData를 여러번 구독하게 되는 문제점을 보완하기 위해 SingleLiveEvent를 활용한다.

 

SingleLiveEvent는 코드 내부에서 하나의 observer만 구독 가능하게 구현된다. 만약, 여러개의 observer가 구독할 경우 어느 곳에서 실행될 지 알 수 없다. (MainActivity에서 특정 SingleLiveEvent를 관찰하고 SubActivity에서도 관찰하게 시킨다면 어느 한 곳만 구독이 된다.)

 

open class SingleLiveEvent<T>() : MutableLiveData<T>() {

    //동시성을 보장하는 AtomicBoolean.
    private val mPending = AtomicBoolean(false)

    /**
     * View(Activity or Fragment 등 LifeCycleOwner)가 활성화 상태가 되거나
     * setValue로 값이 바뀌었을 때 호출되는 observe 함수.
     *
     * 아래의 setValue를 통해서만 pending이 true로 바뀌기 때문에,
     * 구성에 대한 변화가 일어나도 pending은 false이기 때문에 observe가
     * 데이터를 전달하지 않는다!
     */
    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        if (hasActiveObservers()) {
            Timber.w("Multiple observers registered but only one will be notified of changes.")
        }

        // Observe the internal MutableLiveData
        super.observe(owner, Observer { t ->
            if (mPending.compareAndSet(true, false)) {
                observer.onChanged(t)
            }
        })
    }

    /**
     * setValue : UI Thread, 즉 Main Thread에서 실행
     *
     * LiveData로써 들고있는 데이터의 값을 변경하는 함수.
     * 여기서는 pending(AtomicBoolean)의 변수는 true로 바꾸어
     * observe내의 if문을 처리할 수 있도록 하였음.
     */
    @MainThread
    override fun setValue(t: T?) {
        mPending.set(true)
        super.setValue(t)
    }

    // postValue : Background Thread에서 처리 (기존과 같음)
    override fun postValue(value: T) {
        super.postValue(value)
    }

    //T가 void일 경우에 활용
    @MainThread
    fun call() {
        value = null
    }

    companion object {
        private val TAG = "SingleLiveEvent"
    }
}

 

CompareAndSet

운영체제에서 흔히 배우는 동시성을 제공하는 타입에서 활용가능한 API로 예상한 값(expected)과 같을 경우에 갱신 값(update)로 변경한다. 값이 변경된 경우 true를 리턴한다.

compareAndSet(boolean expect, boolean update)

 

즉 아래 코드에서

if (mPending.compareAndSet(true, false)) {
    observer.onChanged(t)
}

mPending이 true이면 false로 바꾸고 true를 리턴하게 되어 데이터의 변화를 발행한다.

 

만약 false였다면 expected(true)와 맞지 않아 false를 리턴하게 되어 데이터의 변화를 발행하지 않을 것이다. 즉, setValue를 한 경우에만 mPending이 true인 상태이기 때문에 observe 내의 if문을 통과할 수 있다.

 

 

setValue() vs postValue()

  • setValue()는 메인 스레드에서 값을 set하는 함수이다.
  • postValue()는 백그라운드 스레드에서 값을 set하는 함수이며 이 요청을 main Looper로 전달하기 때문에 결국 메인 스레드에서 값이 변경되며, 내부적으로 setValue()를 호출한다.

결국 위에서 postValue를 호출하더라도 내부적으로는 setValue가 호출되기 때문에 mPending이 바뀌게 된다.

 

유의사항

위에서 언급한 것 처럼 여러 관찰자를 둘 수 없으므로 한 뷰모델을 여러 프래그먼트가 공유하는 등과 같은 상황에서는 명시적으로 관찰자를 제거해주어야 한다. 

How to use SingleLiveEvent in MVVM + Architecture Component. | by Abhishek Tiwari | Medium

 

참고