Skip to content

Instantly share code, notes, and snippets.

@felipefpx
Created February 2, 2020 23:53
Show Gist options
  • Save felipefpx/df0121be4d1296c6a679ff7e4e14a33f to your computer and use it in GitHub Desktop.
Save felipefpx/df0121be4d1296c6a679ff7e4e14a33f to your computer and use it in GitHub Desktop.
/*
* 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
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val isPending: AtomicBoolean = 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, interceptObserver(observer))
}
override fun observeForever(observer: Observer<in T>) {
super.observeForever(interceptObserver(observer))
}
@MainThread
override fun setValue(@Nullable t: T?) {
isPending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
fun call() {
value = null
}
private fun interceptObserver(observer: Observer<in T>) = Observer<T> { value ->
if (isPending.compareAndSet(true, false)) {
observer.onChanged(value)
}
}
companion object {
private const val TAG = "SingleLiveEvent"
}
}
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertNull
import org.junit.Rule
import org.junit.Test
class SingleLiveEventTest {
@get:Rule
val instantRule = InstantTaskExecutorRule()
@Test
fun `GIVEN single live event, WHEN value not observed, THEN observe value`() {
// Given
val expectedValue = 5
val singleLiveEvent = SingleLiveEvent<Int>().apply { postValue(expectedValue) }
// When / Then
assertEquals(expectedValue, singleLiveEvent.blockingObserve())
}
@Test
fun `GIVEN single live event, WHEN value already observed, THEN observe nothing`() {
// Given
val expectedValue = 5
val singleLiveEvent = SingleLiveEvent<Int>().apply { postValue(expectedValue) }
assertEquals(expectedValue, singleLiveEvent.blockingObserve())
// When / Then
assertNull(singleLiveEvent.blockingObserve())
}
@Test
fun `GIVEN single live event, WHEN no values to observe, THEN observe nothing`() {
// Given
val singleLiveEvent = SingleLiveEvent<Int>()
// When / Then
assertNull(singleLiveEvent.blockingObserve())
}
@Test
fun `GIVEN single live event with value to observe, WHEN calling it, THEN observe nothing`() {
// Given
val singleLiveEvent = SingleLiveEvent<Int>().apply { postValue(5) }
// When
singleLiveEvent.call()
// Then
assertNull(singleLiveEvent.blockingObserve())
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment