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

Commit 47b0a3cd authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "SysUiViewModel.hydratedStateOf" into main

parents ab7548ec 4241d874
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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",
+50 −0
Original line number Diff line number Diff line
@@ -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() {
+32 −0
Original line number Diff line number Diff line
@@ -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()
    }
+12 −0
Original line number Diff line number Diff line
@@ -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()