DEV Community

Tony Robalik
Tony Robalik

Posted on

3 3

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

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()
}
Enter fullscreen mode Exit fullscreen mode

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
    }
}

Enter fullscreen mode Exit fullscreen mode

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)