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