Loading packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt +5 −13 Original line number Diff line number Diff line Loading @@ -19,13 +19,12 @@ package com.android.systemui.lifecycle import android.view.View import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.runtime.staticCompositionLocalOf import androidx.lifecycle.Lifecycle import com.android.app.tracing.TraceUtils import com.android.app.tracing.coroutines.launchTraced as launch import com.android.app.tracing.coroutines.traceCoroutine import com.android.compose.lifecycle.LaunchedEffectWithLifecycle import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlinx.coroutines.CoroutineScope Loading @@ -41,10 +40,6 @@ import kotlinx.coroutines.CoroutineScope * performance findings with actual code. One recommendation: prefer whole string literals instead * of some complex concatenation or templating scheme. * * The remembered view-model is activated every time the [minActiveState] is reached and deactivated * each time the lifecycle state falls "below" the [minActiveState]. This can be used to have more * granular control over when exactly a view-model becomes active. * * Note that, by default, `rememberViewModel` will activate its view-model in the [CoroutineContext] * from which it was called. To configure this, either pass a [coroutineContext] to this method or * use [WithConfiguredRememberViewModels] to bulk-configure all usages of `rememberViewModel`s Loading @@ -54,18 +49,15 @@ import kotlinx.coroutines.CoroutineScope @Composable fun <T> rememberViewModel( traceName: String, minActiveState: Lifecycle.State = Lifecycle.State.STARTED, coroutineContext: CoroutineContext = LocalCoroutineContext.current, key: Any = Unit, coroutineContext: CoroutineContext = LocalCoroutineContext.current, factory: () -> T, ): T { val instance = remember(key) { factory() } if (instance is Activatable) { LaunchedEffectWithLifecycle( key1 = instance, coroutineContext = coroutineContext, minActiveState = minActiveState, ) { // TODO(b/436984081): Pass the coroutineContext once we use LaunchedEffectWithLifecycle // again. LaunchedEffect(instance) { TraceUtils.traceAsync("SystemUI.rememberViewModel", traceName) { traceCoroutine(traceName) { instance.activate() } } Loading packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt→packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt +5 −78 Original line number Diff line number Diff line Loading @@ -20,21 +20,15 @@ package com.android.systemui.lifecycle import android.view.View import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.test.junit4.createComposeRule import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.ui.viewmodel.FakeSysUiViewModel import com.android.systemui.util.Assert import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import kotlin.coroutines.CoroutineContext import kotlin.coroutines.coroutineContext import kotlinx.coroutines.ExperimentalCoroutinesApi Loading @@ -42,6 +36,7 @@ import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.launch import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith Loading Loading @@ -90,7 +85,7 @@ class SysUiViewModelTest : SysuiTestCase() { // return Unit instead of FakeSysUiViewModel. It might be an issue with the compose // compiler. val unused: FakeSysUiViewModel = rememberViewModel("test", key = key) { rememberViewModel("test", key) { when (key) { 1 -> FakeSysUiViewModel( Loading Loading @@ -120,77 +115,6 @@ class SysUiViewModelTest : SysuiTestCase() { assertThat(isActive2).isFalse() } @Test fun rememberActivated_minActiveState_CREATED() { assertActivationThroughAllLifecycleStates(Lifecycle.State.CREATED) } @Test fun rememberActivated_minActiveState_STARTED() { assertActivationThroughAllLifecycleStates(Lifecycle.State.STARTED) } @Test fun rememberActivated_minActiveState_RESUMED() { assertActivationThroughAllLifecycleStates(Lifecycle.State.RESUMED) } private fun assertActivationThroughAllLifecycleStates(minActiveState: Lifecycle.State) { var isActive = false val lifecycleOwner = composeRule.runOnUiThread { object : LifecycleOwner { override val lifecycle = LifecycleRegistry(this) init { lifecycle.currentState = Lifecycle.State.CREATED } } } composeRule.setContent { CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { // Need to explicitly state the type to avoid a weird issue where the factory seems // to return Unit instead of FakeSysUiViewModel. It might be an issue with the // compose compiler. val unused: FakeSysUiViewModel = rememberViewModel(traceName = "test", minActiveState = minActiveState) { FakeSysUiViewModel( onActivation = { isActive = true }, onDeactivation = { isActive = false }, ) } } } // Increase state, step-by-step, all the way to RESUMED, the maximum state and then, reverse // course and decrease the state, step-by-step, all the way back down to CREATED. Lastly, // move to DESTROYED to finish up. // // In each step along the way, verify that our Activatable is active or not, based on the // minActiveState that we received. The Activatable should be active only if the current\ // lifecycle state is equal to or "greater" than the minActiveState. listOf( Lifecycle.State.CREATED, Lifecycle.State.STARTED, Lifecycle.State.RESUMED, Lifecycle.State.STARTED, Lifecycle.State.CREATED, Lifecycle.State.DESTROYED, ) .forEachIndexed { index, lifecycleState -> composeRule.runOnUiThread { lifecycleOwner.lifecycle.currentState = lifecycleState } val expectedIsActive = lifecycleState.isAtLeast(minActiveState) assertWithMessage( "isActive=$isActive but expected to be $expectedIsActive when" + " lifecycleState=$lifecycleState because $lifecycleState is" + " ${if (expectedIsActive) "equal to or greater" else "less"} than" + " minActiveState=$minActiveState (iteration #$index)" ) .that(isActive) .isEqualTo(expectedIsActive) } } @Test fun rememberActivated_leavingTheComposition() { val keepAliveMutable = mutableStateOf(true) Loading @@ -215,6 +139,7 @@ class SysUiViewModelTest : SysuiTestCase() { } @Test @Ignore("b/436984081") fun rememberActivated_withCoroutineContext() { val flag = FlagElement("flag") var viewModel: FakeViewModel? = null Loading @@ -232,6 +157,7 @@ class SysUiViewModelTest : SysuiTestCase() { } @Test @Ignore("b/436984081") fun rememberActivated_withConfiguredCompositionLocal() { val flag = FlagElement("flag") var viewModel: FakeViewModel? = null Loading @@ -246,6 +172,7 @@ class SysUiViewModelTest : SysuiTestCase() { } @Test @Ignore("b/436984081") fun rememberActivated_withConfiguredCompositionLocal_andCoroutineContextOverride() { val configuredFlag = FlagElement("configured") val innerOverrideFlag = FlagElement("innerOverride") Loading Loading
packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt +5 −13 Original line number Diff line number Diff line Loading @@ -19,13 +19,12 @@ package com.android.systemui.lifecycle import android.view.View import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.runtime.staticCompositionLocalOf import androidx.lifecycle.Lifecycle import com.android.app.tracing.TraceUtils import com.android.app.tracing.coroutines.launchTraced as launch import com.android.app.tracing.coroutines.traceCoroutine import com.android.compose.lifecycle.LaunchedEffectWithLifecycle import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlinx.coroutines.CoroutineScope Loading @@ -41,10 +40,6 @@ import kotlinx.coroutines.CoroutineScope * performance findings with actual code. One recommendation: prefer whole string literals instead * of some complex concatenation or templating scheme. * * The remembered view-model is activated every time the [minActiveState] is reached and deactivated * each time the lifecycle state falls "below" the [minActiveState]. This can be used to have more * granular control over when exactly a view-model becomes active. * * Note that, by default, `rememberViewModel` will activate its view-model in the [CoroutineContext] * from which it was called. To configure this, either pass a [coroutineContext] to this method or * use [WithConfiguredRememberViewModels] to bulk-configure all usages of `rememberViewModel`s Loading @@ -54,18 +49,15 @@ import kotlinx.coroutines.CoroutineScope @Composable fun <T> rememberViewModel( traceName: String, minActiveState: Lifecycle.State = Lifecycle.State.STARTED, coroutineContext: CoroutineContext = LocalCoroutineContext.current, key: Any = Unit, coroutineContext: CoroutineContext = LocalCoroutineContext.current, factory: () -> T, ): T { val instance = remember(key) { factory() } if (instance is Activatable) { LaunchedEffectWithLifecycle( key1 = instance, coroutineContext = coroutineContext, minActiveState = minActiveState, ) { // TODO(b/436984081): Pass the coroutineContext once we use LaunchedEffectWithLifecycle // again. LaunchedEffect(instance) { TraceUtils.traceAsync("SystemUI.rememberViewModel", traceName) { traceCoroutine(traceName) { instance.activate() } } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt→packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt +5 −78 Original line number Diff line number Diff line Loading @@ -20,21 +20,15 @@ package com.android.systemui.lifecycle import android.view.View import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.test.junit4.createComposeRule import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.ui.viewmodel.FakeSysUiViewModel import com.android.systemui.util.Assert import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import kotlin.coroutines.CoroutineContext import kotlin.coroutines.coroutineContext import kotlinx.coroutines.ExperimentalCoroutinesApi Loading @@ -42,6 +36,7 @@ import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.launch import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith Loading Loading @@ -90,7 +85,7 @@ class SysUiViewModelTest : SysuiTestCase() { // return Unit instead of FakeSysUiViewModel. It might be an issue with the compose // compiler. val unused: FakeSysUiViewModel = rememberViewModel("test", key = key) { rememberViewModel("test", key) { when (key) { 1 -> FakeSysUiViewModel( Loading Loading @@ -120,77 +115,6 @@ class SysUiViewModelTest : SysuiTestCase() { assertThat(isActive2).isFalse() } @Test fun rememberActivated_minActiveState_CREATED() { assertActivationThroughAllLifecycleStates(Lifecycle.State.CREATED) } @Test fun rememberActivated_minActiveState_STARTED() { assertActivationThroughAllLifecycleStates(Lifecycle.State.STARTED) } @Test fun rememberActivated_minActiveState_RESUMED() { assertActivationThroughAllLifecycleStates(Lifecycle.State.RESUMED) } private fun assertActivationThroughAllLifecycleStates(minActiveState: Lifecycle.State) { var isActive = false val lifecycleOwner = composeRule.runOnUiThread { object : LifecycleOwner { override val lifecycle = LifecycleRegistry(this) init { lifecycle.currentState = Lifecycle.State.CREATED } } } composeRule.setContent { CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { // Need to explicitly state the type to avoid a weird issue where the factory seems // to return Unit instead of FakeSysUiViewModel. It might be an issue with the // compose compiler. val unused: FakeSysUiViewModel = rememberViewModel(traceName = "test", minActiveState = minActiveState) { FakeSysUiViewModel( onActivation = { isActive = true }, onDeactivation = { isActive = false }, ) } } } // Increase state, step-by-step, all the way to RESUMED, the maximum state and then, reverse // course and decrease the state, step-by-step, all the way back down to CREATED. Lastly, // move to DESTROYED to finish up. // // In each step along the way, verify that our Activatable is active or not, based on the // minActiveState that we received. The Activatable should be active only if the current\ // lifecycle state is equal to or "greater" than the minActiveState. listOf( Lifecycle.State.CREATED, Lifecycle.State.STARTED, Lifecycle.State.RESUMED, Lifecycle.State.STARTED, Lifecycle.State.CREATED, Lifecycle.State.DESTROYED, ) .forEachIndexed { index, lifecycleState -> composeRule.runOnUiThread { lifecycleOwner.lifecycle.currentState = lifecycleState } val expectedIsActive = lifecycleState.isAtLeast(minActiveState) assertWithMessage( "isActive=$isActive but expected to be $expectedIsActive when" + " lifecycleState=$lifecycleState because $lifecycleState is" + " ${if (expectedIsActive) "equal to or greater" else "less"} than" + " minActiveState=$minActiveState (iteration #$index)" ) .that(isActive) .isEqualTo(expectedIsActive) } } @Test fun rememberActivated_leavingTheComposition() { val keepAliveMutable = mutableStateOf(true) Loading @@ -215,6 +139,7 @@ class SysUiViewModelTest : SysuiTestCase() { } @Test @Ignore("b/436984081") fun rememberActivated_withCoroutineContext() { val flag = FlagElement("flag") var viewModel: FakeViewModel? = null Loading @@ -232,6 +157,7 @@ class SysUiViewModelTest : SysuiTestCase() { } @Test @Ignore("b/436984081") fun rememberActivated_withConfiguredCompositionLocal() { val flag = FlagElement("flag") var viewModel: FakeViewModel? = null Loading @@ -246,6 +172,7 @@ class SysUiViewModelTest : SysuiTestCase() { } @Test @Ignore("b/436984081") fun rememberActivated_withConfiguredCompositionLocal_andCoroutineContextOverride() { val configuredFlag = FlagElement("configured") val innerOverrideFlag = FlagElement("innerOverride") Loading