diff options
| author | 2023-11-22 15:42:14 +0800 | |
|---|---|---|
| committer | 2023-11-22 18:03:33 +0800 | |
| commit | 9d2409deb5d0c9cad99bbc5bce999bdf5f66078e (patch) | |
| tree | 7454ebdec01c3d1f24013a790bafc7ec9167bdc8 | |
| parent | 54fb3a0414022af997a69651533ea254aa581440 (diff) | |
New OnBackEffect
To detect back pressed, but not consume it.
Fix: 312638864
Test: unit test
Change-Id: I28abb1d84f615bc8ca51aa883ca14d728e2d8bb5
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" + } +} |