코딩하는 일용직 노동자

SingleLiveEvent 로 LiveData 중복 이벤트 처리하기 본문

안드로이드

SingleLiveEvent 로 LiveData 중복 이벤트 처리하기

bacass 2020. 5. 25. 16:49

MVVM 으로 개발할때 ViewModel은 View를 몰라야 합니다.
(ViewModel을 만들고 사용하는 activity가 자신의 context를 넘겨주거나 하지 않습니다.)

Activity는 ViewModel을 observing 한다.

때문에 ViewModel에서 View로 이벤트나 결과를 전달할 경우에는 View가 ViewModel을 observe 하다가 상태가 변하면 그에따른 처리를 하게 합니다.

// ViewModel
var resetList = MutableLiveData<Boolean>().apply {
    value = false
}
...

/**
 * Search 버튼 클릭 처리.
 */
fun onClickSearch() {
    if (!TextUtils.isEmpty(etStr)) {
        mPage = 1
        resetList.value = true
        searchImage(etStr, mPage)
    }
}



// View
viewModel.resetList.observe(viewLifecycleOwner, Observer {
    if (it) {
        viewModel.resetList.value = false
        mImageAdapter?.resetList()
    }
})

onClickSearch() 가 호출되면 resetList의 상태를 바꿔서 View 쪽이 신호를 받게 했습니다.
여기서 문제가 생기게 되는데, 이벤트를 받아서 처리할때마다 viewModel.resetList.value = false 로 다시 상태를 초기화 해주는 번거로움이 있습니다.
실수로 초기화 처리를 하지 않는다면 onClickSearch() 가 호출되더라도 이벤트가 처리되지 않을 것입니다.
또한, false로 초기화를 한다면 observe로 신호가 다시 한번 올것이고 그것을 필터링 하기 위해 if문을 추가해야 했습니다.

LiveData를 이용할때 중복된 신호가 올 경우를 막기위해 MutableLiveData를 상속받아 만든 SingleLiveEvent 로 처리하는 방법이 있습니다.
원본 파일 링크 => SingleLiveEvent.java
setValue() 에서 새로운 이벤트를 받으면 mPending 이 true로 바뀌고 observe 가 호출된다. 이벤트가 처리되면 false 로 바뀝니다.

/*
 *  Copyright 2017 Google Inc.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
import android.util.Log
import androidx.annotation.MainThread
import androidx.annotation.Nullable
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import java.util.concurrent.atomic.AtomicBoolean


/**
 * A lifecycle-aware observable that sends only new updates after subscription, used for events like
 * navigation and Snackbar messages.
 *
 *
 * This avoids a common problem with events: on configuration change (like rotation) an update
 * can be emitted if the observer is active. This LiveData only calls the observable if there's an
 * explicit call to setValue() or call().
 *
 *
 * Note that only one observer is going to be notified of changes.
 */
class SingleLiveEvent<T> : MutableLiveData<T?>() {
    private val mPending = AtomicBoolean(false)

    @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 {
            if (mPending.compareAndSet(true, false)) {
                observer.onChanged(it)
            }
        })
    }

    @MainThread
    override fun setValue(@Nullable t: T?) {
        mPending.set(true)
        super.setValue(t)
    }

    /**
     * Used for cases where T is Void, to make calls cleaner.
     */
    @MainThread
    fun call() {
        value = null
    }

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