From d0eba9956eefd1ea04264f5950717c40ce03eb89 Mon Sep 17 00:00:00 2001 From: Lucas Silva Date: Thu, 13 Apr 2023 16:15:50 -0400 Subject: [PATCH] Add helper to convert a kotlin flow to a Condition This extension function makes it easier to convert a kotlin flow to a Condition for interoperability between the repository pattern which utilizes flows and the Monitor framework. Bug: 277762120 Test: atest ConditionExtensionsTest Change-Id: I1e0b137ff87f489eaaa94799df48fcb86e882358 --- .../shared/condition/ConditionExtensions.kt | 23 +++ .../condition/ConditionExtensionsTest.kt | 135 ++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt new file mode 100644 index 0000000000000..8f8bff86f64da --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt @@ -0,0 +1,23 @@ +package com.android.systemui.shared.condition + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.launch + +/** Converts a boolean flow to a [Condition] object which can be used with a [Monitor] */ +@JvmOverloads +fun Flow.toCondition(scope: CoroutineScope, initialValue: Boolean? = null): Condition { + return object : Condition(initialValue, false) { + var job: Job? = null + + override fun start() { + job = scope.launch { collect { updateCondition(it) } } + } + + override fun stop() { + job?.cancel() + job = null + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt new file mode 100644 index 0000000000000..2b4a7fb4803b5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt @@ -0,0 +1,135 @@ +package com.android.systemui.shared.condition + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidTestingRunner::class) +class ConditionExtensionsTest : SysuiTestCase() { + private lateinit var testScope: TestScope + + @Before + fun setUp() { + testScope = TestScope(StandardTestDispatcher()) + } + + @Test + fun flowInitiallyTrue() = + testScope.runTest { + val flow = flowOf(true) + val condition = flow.toCondition(this) + + runCurrent() + assertThat(condition.isConditionSet).isFalse() + + condition.start() + runCurrent() + assertThat(condition.isConditionSet).isTrue() + assertThat(condition.isConditionMet).isTrue() + } + + @Test + fun flowInitiallyFalse() = + testScope.runTest { + val flow = flowOf(false) + val condition = flow.toCondition(this) + + runCurrent() + assertThat(condition.isConditionSet).isFalse() + + condition.start() + runCurrent() + assertThat(condition.isConditionSet).isTrue() + assertThat(condition.isConditionMet).isFalse() + } + + @Test + fun emptyFlowWithNoInitialValue() = + testScope.runTest { + val flow = emptyFlow() + val condition = flow.toCondition(this) + condition.start() + + runCurrent() + assertThat(condition.isConditionSet).isFalse() + assertThat(condition.isConditionMet).isFalse() + } + + @Test + fun emptyFlowWithInitialValueOfTrue() = + testScope.runTest { + val flow = emptyFlow() + val condition = flow.toCondition(scope = this, initialValue = true) + condition.start() + + runCurrent() + assertThat(condition.isConditionSet).isTrue() + assertThat(condition.isConditionMet).isTrue() + } + + @Test + fun emptyFlowWithInitialValueOfFalse() = + testScope.runTest { + val flow = emptyFlow() + val condition = flow.toCondition(scope = this, initialValue = false) + condition.start() + + runCurrent() + assertThat(condition.isConditionSet).isTrue() + assertThat(condition.isConditionMet).isFalse() + } + + @Test + fun conditionUpdatesWhenFlowEmitsNewValue() = + testScope.runTest { + val flow = MutableStateFlow(false) + val condition = flow.toCondition(this) + condition.start() + + runCurrent() + assertThat(condition.isConditionSet).isTrue() + assertThat(condition.isConditionMet).isFalse() + + flow.value = true + runCurrent() + assertThat(condition.isConditionMet).isTrue() + + flow.value = false + runCurrent() + assertThat(condition.isConditionMet).isFalse() + + condition.stop() + } + + @Test + fun stoppingConditionUnsubscribesFromFlow() = + testScope.runTest { + val flow = MutableSharedFlow() + val condition = flow.toCondition(this) + runCurrent() + assertThat(flow.subscriptionCount.value).isEqualTo(0) + + condition.start() + runCurrent() + assertThat(flow.subscriptionCount.value).isEqualTo(1) + + condition.stop() + runCurrent() + assertThat(flow.subscriptionCount.value).isEqualTo(0) + } +}