summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Chaohui Wang <chaohuiw@google.com> 2023-11-22 15:42:14 +0800
committer Chaohui Wang <chaohuiw@google.com> 2023-11-22 18:03:33 +0800
commit9d2409deb5d0c9cad99bbc5bce999bdf5f66078e (patch)
tree7454ebdec01c3d1f24013a790bafc7ec9167bdc8
parent54fb3a0414022af997a69651533ea254aa581440 (diff)
New OnBackEffect
To detect back pressed, but not consume it. Fix: 312638864 Test: unit test Change-Id: I28abb1d84f615bc8ca51aa883ca14d728e2d8bb5
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/OnBackEffect.kt65
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/OnBackEffectTest.kt82
2 files changed, 147 insertions, 0 deletions
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/OnBackEffect.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/OnBackEffect.kt
new file mode 100644
index 000000000000..3991f26e1b0c
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/OnBackEffect.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 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.activity.OnBackPressedCallback
+import androidx.activity.OnBackPressedDispatcher
+import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.ui.platform.LocalLifecycleOwner
+
+/**
+ * An effect for detecting presses of the system back button, and the back event will not be
+ * consumed by this effect.
+ *
+ * Calling this in your composable adds the given lambda to the [OnBackPressedDispatcher] of the
+ * [LocalOnBackPressedDispatcherOwner].
+ *
+ * @param onBack the action invoked by pressing the system back
+ */
+@Composable
+fun OnBackEffect(onBack: () -> Unit) {
+ val backDispatcher = checkNotNull(LocalOnBackPressedDispatcherOwner.current) {
+ "No OnBackPressedDispatcherOwner was provided via LocalOnBackPressedDispatcherOwner"
+ }.onBackPressedDispatcher
+
+ // Safely update the current `onBack` lambda when a new one is provided
+ val currentOnBack by rememberUpdatedState(onBack)
+ // Remember in Composition a back callback that calls the `onBack` lambda
+ val backCallback = remember {
+ object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ remove()
+ currentOnBack()
+ backDispatcher.onBackPressed()
+ }
+ }
+ }
+ val lifecycleOwner = LocalLifecycleOwner.current
+ DisposableEffect(lifecycleOwner, backDispatcher) {
+ // Add callback to the backDispatcher
+ backDispatcher.addCallback(lifecycleOwner, backCallback)
+ // When the effect leaves the Composition, remove the callback
+ onDispose {
+ backCallback.remove()
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/OnBackEffectTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/OnBackEffectTest.kt
new file mode 100644
index 000000000000..588168636828
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/OnBackEffectTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 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.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.test.hasText
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.waitUntilExists
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.delay
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class OnBackEffectTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private var onBackEffectCalled = false
+
+ @Test
+ fun onBackEffect() {
+ composeTestRule.setContent {
+ TestNavHost {
+ val navController = LocalNavController.current
+ LaunchedEffect(Unit) {
+ navController.navigate(ROUTE_B)
+ delay(100)
+ navController.navigateBack()
+ }
+ }
+ }
+
+ composeTestRule.waitUntilExists(hasText(ROUTE_A))
+ assertThat(onBackEffectCalled).isTrue()
+ }
+
+ @Composable
+ private fun TestNavHost(content: @Composable () -> Unit) {
+ val navController = rememberNavController()
+ CompositionLocalProvider(navController.localNavController()) {
+ NavHost(navController, ROUTE_A) {
+ composable(route = ROUTE_A) { Text(ROUTE_A) }
+ composable(route = ROUTE_B) {
+ Text(ROUTE_B)
+
+ OnBackEffect {
+ onBackEffectCalled = true
+ }
+ }
+ }
+ content()
+ }
+ }
+
+ private companion object {
+ const val ROUTE_A = "RouteA"
+ const val ROUTE_B = "RouteB"
+ }
+}