diff options
4 files changed, 96 insertions, 0 deletions
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 3aa89eec54ff..c1bb55c70073 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -893,6 +893,7 @@ android_robolectric_test { ], static_libs: [ "RoboTestLibraries", + "androidx.compose.runtime_runtime", ], libs: [ "android.test.runner", @@ -929,6 +930,7 @@ android_robolectric_test { ], static_libs: [ "RoboTestLibraries", + "androidx.compose.runtime_runtime", ], libs: [ "android.test.runner", diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt index 976dc52b2602..7d5722035a14 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt @@ -17,8 +17,14 @@ package com.android.systemui.lifecycle import android.view.View +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.Text import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.assertTextEquals +import androidx.compose.ui.test.hasTestTag import androidx.compose.ui.test.junit4.createComposeRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -26,6 +32,8 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.util.Assert import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -149,6 +157,48 @@ class SysUiViewModelTest : SysuiTestCase() { assertThat(viewModel.isActivated).isTrue() } + + @Test + fun hydratedStateOf() { + val keepAliveMutable = mutableStateOf(true) + val upstreamStateFlow = MutableStateFlow(true) + val upstreamFlow = upstreamStateFlow.map { !it } + composeRule.setContent { + val keepAlive by keepAliveMutable + if (keepAlive) { + val viewModel = rememberViewModel { + FakeSysUiViewModel( + upstreamFlow = upstreamFlow, + upstreamStateFlow = upstreamStateFlow, + ) + } + + Column { + Text( + "upstreamStateFlow=${viewModel.stateBackedByStateFlow}", + Modifier.testTag("upstreamStateFlow") + ) + Text( + "upstreamFlow=${viewModel.stateBackedByFlow}", + Modifier.testTag("upstreamFlow") + ) + } + } + } + + composeRule.waitForIdle() + composeRule + .onNode(hasTestTag("upstreamStateFlow")) + .assertTextEquals("upstreamStateFlow=true") + composeRule.onNode(hasTestTag("upstreamFlow")).assertTextEquals("upstreamFlow=false") + + composeRule.runOnUiThread { upstreamStateFlow.value = false } + composeRule.waitForIdle() + composeRule + .onNode(hasTestTag("upstreamStateFlow")) + .assertTextEquals("upstreamStateFlow=false") + composeRule.onNode(hasTestTag("upstreamFlow")).assertTextEquals("upstreamFlow=true") + } } private class FakeViewModel : SysUiViewModel() { diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt index 104b076fc786..32c476093774 100644 --- a/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt @@ -18,13 +18,45 @@ package com.android.systemui.lifecycle import android.view.View import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.snapshots.StateFactoryMarker import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch /** Base class for all System UI view-models. */ abstract class SysUiViewModel : BaseActivatable() { + @StateFactoryMarker + fun <T> hydratedStateOf( + source: StateFlow<T>, + ): State<T> { + return hydratedStateOf( + initialValue = source.value, + source = source, + ) + } + + @StateFactoryMarker + fun <T> hydratedStateOf( + initialValue: T, + source: Flow<T>, + ): State<T> { + val mutableState = mutableStateOf(initialValue) + addChild( + object : BaseActivatable() { + override suspend fun onActivated(): Nothing { + source.collect { mutableState.value = it } + awaitCancellation() + } + } + ) + return mutableState + } + override suspend fun onActivated(): Nothing { awaitCancellation() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt index c0bb9a6fbe14..90cd8c766da8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt @@ -16,15 +16,27 @@ package com.android.systemui.lifecycle +import androidx.compose.runtime.getValue import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.flowOf class FakeSysUiViewModel( private val onActivation: () -> Unit = {}, private val onDeactivation: () -> Unit = {}, + private val upstreamFlow: Flow<Boolean> = flowOf(true), + private val upstreamStateFlow: StateFlow<Boolean> = MutableStateFlow(true).asStateFlow(), ) : SysUiViewModel() { + var activationCount = 0 var cancellationCount = 0 + val stateBackedByFlow: Boolean by hydratedStateOf(initialValue = true, source = upstreamFlow) + val stateBackedByStateFlow: Boolean by hydratedStateOf(source = upstreamStateFlow) + override suspend fun onActivated(): Nothing { activationCount++ onActivation() |