[Android/Kotlin] SingleLiveEvent

hyomin·2022년 4월 16일
0

TIL

목록 보기
4/14

ViewModel 에서 View 에 이벤트를 전달할 때, 값을 전달하는 경우가 아닌 이벤트가 발생했다는 사실만을 전달하고 싶을 때,

class ListViewModel : ViewModel {
    private val _navigateToDetails = SingleLiveEvent<Any>()

    val navigateToDetails : LiveData<Any>
        get() = _navigateToDetails


    fun userClicksOnButton() {
        _navigateToDetails.call()
    }
}

in View


myViewModel.navigateToDetails.observe(this, Observer {
    if (it) startActivity(DetailsActivity...)
})

SingleLiveEvent라는 MutableLiveData를 상속한 클래스를 만듭니다.
이벤트를 발생시키기 위해서 데이터를 변경 시키는 로직을 내부에 두고 외부에서는 call()을 호출하도록 하는것입니다.
만약 함수의 파라미터가 필요하다면 제네릭 T를 정의하고 setValue()를 이용할 수도 있습니다.

sample 1

public class SingleLiveEvent<T> extends MutableLiveData<T> {

    private static final String TAG = "SingleLiveEvent";

    private static final long MIN_CLICK_INTERVAL = 200;
    private long lastClickTime;

    private final AtomicBoolean mPending = new AtomicBoolean(false);

    ...
    @MainThread
    public void setValue(@Nullable T t) {
        long currentClickTime = SystemClock.uptimeMillis();
        long elapsedTime = currentClickTime - lastClickTime;
        lastClickTime = currentClickTime;

        if(elapsedTime <=MIN_CLICK_INTERVAL){
            return;
        }
        mPending.set(true);
        super.setValue(t);
    }

    ...
}
                                             

기존 SingleLiveEvent코드와 거의 동일하며, 마지막 이벤트 처리 시각을 기록하여 현재시간과 비교했을 때 Threshold(임계값)을 초과 하는지 확인한 후 invoke 하는 내용이다.

특별한 것은 없지만, Databinding과 RxJava에 대한 의존성을 제거 할 수 있어 매우 쾌적해졌다.

postValue(T)를 사용할 수도 있지만, 임계값이 보장이 되지 않기 때문에 이러한 방법을 사용하는 것이 가장 확실한 방법이라고 생각한다.

sample 2

import android.util.Log
import androidx.annotation.MainThread
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import java.util.concurrent.atomic.AtomicBoolean

class SingleLiveEvent<T> : MutableLiveData<T>() {
    /**
    * 멀티쓰레딩 환경에서 동시성을 보장하는 AtomicBoolean.
    * false로 초기화되어 있음
    */
    private val pending = AtomicBoolean(false)
    
    /**
    * View(Activity or Fragment 등 LifeCycleOwner)가 활성화 상태가 되거나
    * setValue로 값이 바뀌었을 때 호출되는 observe 함수.
    * pending.compareAndSet(true, false)라는 것은, 위의 pending 변수가
    * true면 if문 내의 로직을 처리하고 false로 바꾼다는 것이다.
    *
    * 아래의 setValue를 통해서만 pending값이 true로 바뀌기 때문에,
    * Configuration Changed가 일어나도 pending값은 false이기 때문에 observe가
    * 데이터를 전달하지 않는다!
    */
    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        if (hasActiveObservers()) {
            Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
        }
        // Observe the internal MutableLiveData
        super.observe(owner, Observer { t ->
            if (pending.compareAndSet(true, false)) {
                observer.onChanged(t)
            }
        })
    }

    /**
    * LiveData로써 들고있는 데이터의 값을 변경하는 함수.
    * 여기서는 pending(AtomicBoolean)의 변수는 true로 바꾸어 
    * observe내의 if문을 처리할 수 있도록 하였음.
    */
    @MainThread
    override fun setValue(t: T?) {
        pending.set(true)
        super.setValue(t)
    }
    
    /**
    * 데이터의 속성을 지정해주지 않아도 call만으로 setValue를 호출 가능
    */
    @MainThread
    fun call() {
        value = null
    }
    
    companion object {
        private val TAG = "SingleLiveEvent"
    }
}

참고
https://velog.io/@hhi-5258/AAC-SingleLiveEvent
https://zladnrms.tistory.com/146

profile
🌱

0개의 댓글