1 /* 2 * Copyright 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.compose.runtime.saveable 18 19 import android.os.Bundle 20 import androidx.activity.ComponentActivity 21 import androidx.compose.runtime.CompositionLocalProvider 22 import androidx.compose.runtime.MutableState 23 import androidx.compose.runtime.getValue 24 import androidx.compose.runtime.mutableStateOf 25 import androidx.compose.runtime.remember 26 import androidx.compose.runtime.setValue 27 import androidx.compose.ui.test.junit4.StateRestorationTester 28 import androidx.compose.ui.test.junit4.createAndroidComposeRule 29 import androidx.test.ext.junit.runners.AndroidJUnit4 30 import androidx.test.filters.MediumTest 31 import com.google.common.truth.Truth.assertThat 32 import org.junit.Rule 33 import org.junit.Test 34 import org.junit.runner.RunWith 35 36 @Suppress("RememberReturnType") 37 @MediumTest 38 @RunWith(AndroidJUnit4::class) 39 class SaveableStateHolderTest { 40 41 @get:Rule val rule = createAndroidComposeRule<Activity>() 42 43 private val restorationTester = StateRestorationTester(rule) 44 45 @Test stateIsRestoredWhenGoBackToScreen1null46 fun stateIsRestoredWhenGoBackToScreen1() { 47 var increment = 0 48 var screen by mutableStateOf(Screens.Screen1) 49 var numberOnScreen1 = -1 50 var restorableNumberOnScreen1 = -1 51 restorationTester.setContent { 52 val holder = rememberSaveableStateHolder() 53 holder.SaveableStateProvider(screen) { 54 if (screen == Screens.Screen1) { 55 numberOnScreen1 = remember { increment++ } 56 restorableNumberOnScreen1 = rememberSaveable { increment++ } 57 } else { 58 // screen 2 59 remember { 100 } 60 } 61 } 62 } 63 64 rule.runOnIdle { 65 assertThat(numberOnScreen1).isEqualTo(0) 66 assertThat(restorableNumberOnScreen1).isEqualTo(1) 67 screen = Screens.Screen2 68 } 69 70 // wait for the screen switch to apply 71 rule.runOnIdle { 72 numberOnScreen1 = -1 73 restorableNumberOnScreen1 = -1 74 // switch back to screen1 75 screen = Screens.Screen1 76 } 77 78 rule.runOnIdle { 79 assertThat(numberOnScreen1).isEqualTo(2) 80 assertThat(restorableNumberOnScreen1).isEqualTo(1) 81 } 82 } 83 84 @Test simpleRestoreOnlyOneScreennull85 fun simpleRestoreOnlyOneScreen() { 86 var increment = 0 87 var number = -1 88 var restorableNumber = -1 89 restorationTester.setContent { 90 val holder = rememberSaveableStateHolder() 91 holder.SaveableStateProvider(Screens.Screen1) { 92 number = remember { increment++ } 93 restorableNumber = rememberSaveable { increment++ } 94 } 95 } 96 97 rule.runOnIdle { 98 assertThat(number).isEqualTo(0) 99 assertThat(restorableNumber).isEqualTo(1) 100 number = -1 101 restorableNumber = -1 102 } 103 104 restorationTester.emulateSavedInstanceStateRestore() 105 106 rule.runOnIdle { 107 assertThat(number).isEqualTo(2) 108 assertThat(restorableNumber).isEqualTo(1) 109 } 110 } 111 112 @Test switchToScreen2AndRestorenull113 fun switchToScreen2AndRestore() { 114 var increment = 0 115 var screen by mutableStateOf(Screens.Screen1) 116 var numberOnScreen2 = -1 117 var restorableNumberOnScreen2 = -1 118 restorationTester.setContent { 119 val holder = rememberSaveableStateHolder() 120 holder.SaveableStateProvider(screen) { 121 if (screen == Screens.Screen2) { 122 numberOnScreen2 = remember { increment++ } 123 restorableNumberOnScreen2 = rememberSaveable { increment++ } 124 } 125 } 126 } 127 128 rule.runOnIdle { screen = Screens.Screen2 } 129 130 // wait for the screen switch to apply 131 rule.runOnIdle { 132 assertThat(numberOnScreen2).isEqualTo(0) 133 assertThat(restorableNumberOnScreen2).isEqualTo(1) 134 numberOnScreen2 = -1 135 restorableNumberOnScreen2 = -1 136 } 137 138 restorationTester.emulateSavedInstanceStateRestore() 139 140 rule.runOnIdle { 141 assertThat(numberOnScreen2).isEqualTo(2) 142 assertThat(restorableNumberOnScreen2).isEqualTo(1) 143 } 144 } 145 146 @Test stateOfScreen1IsSavedAndRestoredWhileWeAreOnScreen2null147 fun stateOfScreen1IsSavedAndRestoredWhileWeAreOnScreen2() { 148 var increment = 0 149 var screen by mutableStateOf(Screens.Screen1) 150 var numberOnScreen1 = -1 151 var restorableNumberOnScreen1 = -1 152 restorationTester.setContent { 153 val holder = rememberSaveableStateHolder() 154 holder.SaveableStateProvider(screen) { 155 if (screen == Screens.Screen1) { 156 numberOnScreen1 = remember { increment++ } 157 restorableNumberOnScreen1 = rememberSaveable { increment++ } 158 } else { 159 // screen 2 160 remember { 100 } 161 } 162 } 163 } 164 165 rule.runOnIdle { 166 assertThat(numberOnScreen1).isEqualTo(0) 167 assertThat(restorableNumberOnScreen1).isEqualTo(1) 168 screen = Screens.Screen2 169 } 170 171 // wait for the screen switch to apply 172 rule.runOnIdle { 173 numberOnScreen1 = -1 174 restorableNumberOnScreen1 = -1 175 } 176 177 restorationTester.emulateSavedInstanceStateRestore() 178 179 // switch back to screen1 180 rule.runOnIdle { screen = Screens.Screen1 } 181 182 rule.runOnIdle { 183 assertThat(numberOnScreen1).isEqualTo(2) 184 assertThat(restorableNumberOnScreen1).isEqualTo(1) 185 } 186 } 187 188 @Test weCanSkipSavingForCurrentScreennull189 fun weCanSkipSavingForCurrentScreen() { 190 var increment = 0 191 var screen by mutableStateOf(Screens.Screen1) 192 var restorableStateHolder: SaveableStateHolder? = null 193 var restorableNumberOnScreen1 = -1 194 restorationTester.setContent { 195 val holder = rememberSaveableStateHolder() 196 restorableStateHolder = holder 197 holder.SaveableStateProvider(screen) { 198 if (screen == Screens.Screen1) { 199 restorableNumberOnScreen1 = rememberSaveable { increment++ } 200 } else { 201 // screen 2 202 remember { 100 } 203 } 204 } 205 } 206 207 rule.runOnIdle { 208 assertThat(restorableNumberOnScreen1).isEqualTo(0) 209 restorableNumberOnScreen1 = -1 210 restorableStateHolder!!.removeState(Screens.Screen1) 211 screen = Screens.Screen2 212 } 213 214 rule.runOnIdle { 215 // switch back to screen1 216 screen = Screens.Screen1 217 } 218 219 rule.runOnIdle { assertThat(restorableNumberOnScreen1).isEqualTo(1) } 220 } 221 222 @Test weCanRemoveAlreadySavedStatenull223 fun weCanRemoveAlreadySavedState() { 224 var increment = 0 225 var screen by mutableStateOf(Screens.Screen1) 226 var restorableStateHolder: SaveableStateHolder? = null 227 var restorableNumberOnScreen1 = -1 228 restorationTester.setContent { 229 val holder = rememberSaveableStateHolder() 230 restorableStateHolder = holder 231 holder.SaveableStateProvider(screen) { 232 if (screen == Screens.Screen1) { 233 restorableNumberOnScreen1 = rememberSaveable { increment++ } 234 } else { 235 // screen 2 236 remember { 100 } 237 } 238 } 239 } 240 241 rule.runOnIdle { 242 assertThat(restorableNumberOnScreen1).isEqualTo(0) 243 restorableNumberOnScreen1 = -1 244 screen = Screens.Screen2 245 } 246 247 rule.runOnIdle { 248 // switch back to screen1 249 restorableStateHolder!!.removeState(Screens.Screen1) 250 screen = Screens.Screen1 251 } 252 253 rule.runOnIdle { assertThat(restorableNumberOnScreen1).isEqualTo(1) } 254 } 255 256 @Test restoringStateOfThePreviousPageAfterCreatingBundlenull257 fun restoringStateOfThePreviousPageAfterCreatingBundle() { 258 var showFirstPage by mutableStateOf(true) 259 var firstPageState: MutableState<Int>? = null 260 261 rule.setContent { 262 val holder = rememberSaveableStateHolder() 263 holder.SaveableStateProvider(showFirstPage) { 264 if (showFirstPage) { 265 firstPageState = rememberSaveable { mutableStateOf(0) } 266 } 267 } 268 } 269 270 rule.runOnIdle { 271 assertThat(firstPageState!!.value).isEqualTo(0) 272 // change the value, so we can assert this change will be restored 273 firstPageState!!.value = 1 274 firstPageState = null 275 showFirstPage = false 276 } 277 278 rule.runOnIdle { 279 rule.activity.doFakeSave() 280 showFirstPage = true 281 } 282 283 rule.runOnIdle { assertThat(firstPageState!!.value).isEqualTo(1) } 284 } 285 286 @Test saveNothingWhenNoRememberSaveableIsUsedInternallynull287 fun saveNothingWhenNoRememberSaveableIsUsedInternally() { 288 var showFirstPage by mutableStateOf(true) 289 val registry = SaveableStateRegistry(null) { true } 290 291 rule.setContent { 292 CompositionLocalProvider(LocalSaveableStateRegistry provides registry) { 293 val holder = rememberSaveableStateHolder() 294 holder.SaveableStateProvider(showFirstPage) {} 295 } 296 } 297 298 rule.runOnIdle { showFirstPage = false } 299 300 rule.runOnIdle { 301 val savedData = registry.performSave() 302 assertThat(savedData).isEqualTo(emptyMap<String, List<Any?>>()) 303 } 304 } 305 306 class Activity : ComponentActivity() { doFakeSavenull307 fun doFakeSave() { 308 onSaveInstanceState(Bundle()) 309 } 310 } 311 } 312 313 enum class Screens { 314 Screen1, 315 Screen2, 316 } 317