loading...

A Custom LiveData that has onActive and onInactive Listeners -- Tested

autonomousapps profile image Tony Robalik ・2 min read

I was expecting my next post to continue my series on dagger.android, but today's adventures were too strange not to relate to others.

Need: a LiveData object that would "do something" when it had an active observer.

Observation: LiveData has two hooks: onActive() and onInactive() that are designed for just this purpose.

Plan: Extend MutableLiveData with a class named ActiveMutableLiveData that does something with these hooks.

Further observation: if I'm going to push this to production, I should test it to prove to myself I understand what's going on and that my new class behaves as expected.

ActiveMutableLiveData

/**
 * A form of [MutableLiveData][android.arch.lifecycle.MutableLiveData] that 
 * provides hooks 
for for [onActive][android.arch.lifecycle.MutableLiveData.onActive] and
 * [onInactive][android.arch.lifecycle.MutableLiveData.onInactive]. Use these to be 
 * notified when this [LiveData][android.arch.lifecycle.LiveData] gains and loses an 
 * active observer.
 */
class ActiveMutableLiveData<T>(
    private val onActiveListener: OnActiveListener
) : MutableLiveData<T>() {

    constructor(onActive: () -> Unit = {}, onInactive: () -> Unit = {}) : this(
        onActiveListenerFromLambdas(onActive, onInactive)
    )

    override fun onActive() {
        onActiveListener.onActive()
    }

    override fun onInactive() {
        onActiveListener.onInactive()
    }
}

fun onActiveListenerFromLambdas(
    onActive: () -> Unit, 
    onInactive: () -> Unit
): OnActiveListener {

    return object : OnActiveListener {
        override fun onActive() {
            onActive.invoke()
        }

        override fun onInactive() {
            onInactive.invoke()
        }
    }
}

interface OnActiveListener {
    fun onActive()
    fun onInactive()
}

ActiveMutableLiveDataTest

// Using JUnit5
internal class ActiveMutableLiveDataTest {

    private val counter = AtomicInteger()
    private val liveData = ActiveMutableLiveData<Boolean>(onActive = {
        counter.incrementAndGet() 
    })

    @Test fun whenObjectBecomesActive_thenOnActiveListenerIsTriggered() {
        // Given
        val lifecycle = TestLifecycle()
        liveData.observe(lifecycle, Observer {
            println("$it")
        })

        // verify initial condition
        assertEquals(0, counter.get())

        // When
        lifecycle.state = Lifecycle.State.STARTED

        // Then
        assertEquals(1, counter.get())
    }

    @Test fun expectOnActiveListenerTriggeredEveryTime() {
        // Given
        val lifecycle = TestLifecycle()
        liveData.observe(lifecycle, Observer {
            println("$it")
        })

        // verify initial condition
        assertEquals(0, counter.get())

        // When
        lifecycle.state = Lifecycle.State.STARTED
        lifecycle.state = Lifecycle.State.CREATED
        lifecycle.state = Lifecycle.State.STARTED

        // Then
        assertEquals(2, counter.get())
    }

    class TestLifecycle : LifecycleOwner {

        var state: Lifecycle.State = Lifecycle.State.INITIALIZED
            set(value) {
                registry.markState(value)
            }

        private val lifecycle = object : Lifecycle() {
            override fun addObserver(observer: LifecycleObserver) {
                // don't care
            }

            override fun removeObserver(observer: LifecycleObserver) {
                // don't care
            }

            override fun getCurrentState(): State = state
        }

        private val registry = LifecycleRegistry({ lifecycle })

        override fun getLifecycle() = registry
    }
}

Posted on Apr 27 '18 by:

Discussion

markdown guide