Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 45f338a0 authored by Jordan Demeulenaere's avatar Jordan Demeulenaere Committed by Android (Google) Code Review
Browse files

Merge "Revert "[flexiglass] rememberViewModel uses lifecycle"" into main

parents 2022b130 8e42ae27
Loading
Loading
Loading
Loading
+5 −13
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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() }
            }
+5 −78
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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(
@@ -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)
@@ -215,6 +139,7 @@ class SysUiViewModelTest : SysuiTestCase() {
    }

    @Test
    @Ignore("b/436984081")
    fun rememberActivated_withCoroutineContext() {
        val flag = FlagElement("flag")
        var viewModel: FakeViewModel? = null
@@ -232,6 +157,7 @@ class SysUiViewModelTest : SysuiTestCase() {
    }

    @Test
    @Ignore("b/436984081")
    fun rememberActivated_withConfiguredCompositionLocal() {
        val flag = FlagElement("flag")
        var viewModel: FakeViewModel? = null
@@ -246,6 +172,7 @@ class SysUiViewModelTest : SysuiTestCase() {
    }

    @Test
    @Ignore("b/436984081")
    fun rememberActivated_withConfiguredCompositionLocal_andCoroutineContextOverride() {
        val configuredFlag = FlagElement("configured")
        val innerOverrideFlag = FlagElement("innerOverride")