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

Commit 527fbf9a authored by Chaohui Wang's avatar Chaohui Wang
Browse files

Create LifecycleEffect to listen for lifecycle

Bug: 236346018
Test: Unit test
Change-Id: I264f7215914d92dc32120254b355ba75be225f42
parent 8bdd5306
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -88,6 +88,7 @@ dependencies {
    implementation "com.airbnb.android:lottie-compose:5.2.0"

    androidTestImplementation project(":testutils")
    androidTestImplementation 'androidx.lifecycle:lifecycle-runtime-testing'
    androidTestImplementation "com.linkedin.dexmaker:dexmaker-mockito:2.28.1"
}

+46 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settingslib.spa.framework.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver

@Composable
fun LifecycleEffect(
    onStart: () -> Unit = {},
    onStop: () -> Unit = {},
) {
    val lifecycleOwner = LocalLifecycleOwner.current
    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            if (event == Lifecycle.Event.ON_START) {
                onStart()
            } else if (event == Lifecycle.Event.ON_STOP) {
                onStop()
            }
        }

        lifecycleOwner.lifecycle.addObserver(observer)

        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }
}
 No newline at end of file
+21 −35
Original line number Diff line number Diff line
@@ -18,35 +18,37 @@ package com.android.settingslib.spa.framework.util

import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.core.os.bundleOf
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import com.android.settingslib.spa.framework.common.LOG_DATA_DISPLAY_NAME
import com.android.settingslib.spa.framework.common.LOG_DATA_SESSION_NAME
import com.android.settingslib.spa.framework.common.LogCategory
import com.android.settingslib.spa.framework.common.LogEvent
import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.LifecycleEffect
import com.android.settingslib.spa.framework.compose.LocalNavController
import com.android.settingslib.spa.framework.compose.NavControllerWrapper

@Composable
internal fun SettingsPageProvider.PageEvent(arguments: Bundle? = null) {
    val page = remember(arguments) { createSettingsPage(arguments) }
    val lifecycleOwner = LocalLifecycleOwner.current
    val navController = LocalNavController.current
    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            val logPageEvent: (event: LogEvent) -> Unit = {
    LifecycleEffect(
        onStart = { page.logPageEvent(LogEvent.PAGE_ENTER, navController) },
        onStop = { page.logPageEvent(LogEvent.PAGE_LEAVE, navController) },
    )
}

private fun SettingsPage.logPageEvent(event: LogEvent, navController: NavControllerWrapper) {
    SpaEnvironmentFactory.instance.logger.event(
                    id = page.id,
                    event = it,
        id = id,
        event = event,
        category = LogCategory.FRAMEWORK,
        extraData = bundleOf(
                        LOG_DATA_DISPLAY_NAME to page.displayName,
            LOG_DATA_DISPLAY_NAME to displayName,
            LOG_DATA_SESSION_NAME to navController.sessionSourceName,
        ).apply {
            val normArguments = parameter.normalize(arguments)
@@ -54,19 +56,3 @@ internal fun SettingsPageProvider.PageEvent(arguments: Bundle? = null) {
        }
    )
}
 No newline at end of file
            if (event == Lifecycle.Event.ON_START) {
                logPageEvent(LogEvent.PAGE_ENTER)
            } else if (event == Lifecycle.Event.ON_STOP) {
                logPageEvent(LogEvent.PAGE_LEAVE)
            }
        }

        // Add the observer to the lifecycle
        lifecycleOwner.lifecycle.addObserver(observer)

        // When the effect leaves the Composition, remove the observer
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ android_test {
        "SpaLib",
        "SpaLibTestUtils",
        "androidx.compose.runtime_runtime",
        "androidx.lifecycle_lifecycle-runtime-testing",
        "androidx.test.ext.junit",
        "androidx.test.runner",
        "mockito-target-minus-junit4",
+62 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settingslib.spa.framework.compose

import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.testing.TestLifecycleOwner
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class LifecycleEffectTest {
    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun onStart_isCalled() {
        var onStartIsCalled = false
        composeTestRule.setContent {
            LifecycleEffect(onStart = { onStartIsCalled = true })
        }

        assertThat(onStartIsCalled).isTrue()
    }

    @Test
    fun onStop_isCalled() {
        var onStopIsCalled = false
        val testLifecycleOwner = TestLifecycleOwner()

        composeTestRule.setContent {
            CompositionLocalProvider(LocalLifecycleOwner provides testLifecycleOwner) {
                LifecycleEffect(onStop = { onStopIsCalled = true })
            }
            LaunchedEffect(Unit) {
                testLifecycleOwner.currentState = Lifecycle.State.CREATED
            }
        }

        assertThat(onStopIsCalled).isTrue()
    }
}
 No newline at end of file
Loading