[go: nahoru, domu]

blob: f63399f56266fcc9ca62975d58bdd12df25d5e88 [file] [log] [blame]
/*
* Copyright 2021 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 androidx.fragment.app
import androidx.fragment.app.test.FragmentTestActivity
import androidx.fragment.app.test.TestViewModel
import androidx.fragment.test.R
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.testutils.withActivity
import androidx.testutils.withUse
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import leakcanary.DetectLeaksAfterTestSuccess
import org.junit.Assert.fail
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@LargeTest
@RunWith(AndroidJUnit4::class)
class SaveRestoreBackStackTest {
@get:Rule
val rule = DetectLeaksAfterTestSuccess()
@Test
fun saveBackStack() {
withUse(ActivityScenario.launch(FragmentTestActivity::class.java)) {
val fm = withActivity {
supportFragmentManager
}
val fragmentBase = StrictViewFragment()
val fragmentReplacement = StateSaveFragment()
fm.beginTransaction()
.add(R.id.content, fragmentBase)
.commit()
executePendingTransactions()
fm.beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.content, fragmentReplacement)
.addToBackStack("replacement")
.commit()
executePendingTransactions()
assertWithMessage("ViewModel should not be cleared after commit()")
.that(fragmentReplacement.viewModel.cleared)
.isFalse()
fm.saveBackStack("replacement")
executePendingTransactions()
assertWithMessage("Saved Fragments should have their state saved")
.that(fragmentReplacement.calledOnSaveInstanceState)
.isTrue()
// Saved Fragments should be destroyed
assertWithMessage("Saved Fragments should be destroyed")
.that(fragmentReplacement.calledOnDestroy)
.isTrue()
// But any ViewModels should not be cleared so that they're available
// for later restoration
assertWithMessage("ViewModel should not be cleared after saveBackStack()")
.that(fragmentReplacement.viewModel.cleared)
.isFalse()
moveToState(Lifecycle.State.DESTROYED)
assertWithMessage("ViewModel should be cleared after the activity is fully destroyed")
.that(fragmentReplacement.viewModel.cleared)
.isTrue()
}
}
@Test
fun saveBackStackWithoutExecutePendingTransactions() {
withUse(ActivityScenario.launch(FragmentTestActivity::class.java)) {
val fm = withActivity {
supportFragmentManager
}
val fragmentBase = StrictViewFragment()
val fragmentReplacement = StateSaveFragment()
fm.beginTransaction()
.add(R.id.content, fragmentBase)
.commit()
executePendingTransactions()
withActivity {
fm.beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.content, fragmentReplacement)
.addToBackStack("replacement")
.commit()
// Immediately save the back stack without calling executePendingTransactions
fm.saveBackStack("replacement")
}
executePendingTransactions()
// Now restore the back stack to ensure that the replacement fragment is restored
fm.restoreBackStack("replacement")
executePendingTransactions()
val newFragmentReplacement = fm.findFragmentById(R.id.content)
assertThat(newFragmentReplacement).isInstanceOf(StateSaveFragment::class.java)
assertThat(fm.backStackEntryCount).isEqualTo(1)
}
}
@Test
@Ignore("b/308684873")
fun saveBackStackAddedWithoutExecutePendingTransactions() {
withUse(ActivityScenario.launch(FragmentTestActivity::class.java)) {
val fm = withActivity {
supportFragmentManager
}
val fragmentBase = StrictViewFragment()
val fragmentReplacement = StateSaveFragment()
fm.beginTransaction()
.add(R.id.content, fragmentBase)
.commit()
executePendingTransactions()
withActivity {
// Now we add the new transaction and immediately do a save+restore
// without calling executePendingTransactions
fm.beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.content, fragmentReplacement)
.addToBackStack("replacement")
.commit()
fm.saveBackStack("replacement")
fm.restoreBackStack("replacement")
}
executePendingTransactions()
val newFragmentReplacement = fm.findFragmentById(R.id.content)
assertThat(newFragmentReplacement).isInstanceOf(StateSaveFragment::class.java)
assertThat(fm.backStackEntryCount).isEqualTo(1)
}
}
@Test
fun savePreviouslyReferencedFragment() {
withUse(ActivityScenario.launch(FragmentTestActivity::class.java)) {
val fm = withActivity {
supportFragmentManager
}
val fragmentBase = StrictViewFragment()
val fragmentReplacement = StrictViewFragment()
fm.beginTransaction()
.add(R.id.content, fragmentBase)
.commit()
executePendingTransactions()
fm.beginTransaction()
.setReorderingAllowed(true)
.remove(fragmentBase)
.add(R.id.content, fragmentReplacement)
.addToBackStack("replacement")
.commit()
executePendingTransactions()
try {
withActivity {
fm.saveBackStack("replacement")
fm.executePendingTransactions()
}
fail("executePendingTransactions() should fail with an IllegalArgumentException")
} catch (e: IllegalArgumentException) {
assertThat(e)
.hasMessageThat()
.startsWith(
"saveBackStack(\"replacement\") must be self contained and not " +
"reference fragments from non-saved FragmentTransactions."
)
}
}
}
@Test
fun saveNonReorderingAllowedTransaction() {
withUse(ActivityScenario.launch(FragmentTestActivity::class.java)) {
val fm = withActivity {
supportFragmentManager
}
val fragmentBase = StrictViewFragment()
val fragmentReplacement = StrictViewFragment()
fm.beginTransaction()
.add(R.id.content, fragmentBase)
.commit()
executePendingTransactions()
fm.beginTransaction()
.replace(R.id.content, fragmentReplacement)
.addToBackStack("replacement")
.commit()
executePendingTransactions()
try {
withActivity {
fm.saveBackStack("replacement")
fm.executePendingTransactions()
}
fail("executePendingTransactions() should fail with an IllegalArgumentException")
} catch (e: IllegalArgumentException) {
assertThat(e)
.hasMessageThat()
.startsWith(
"saveBackStack(\"replacement\") included FragmentTransactions must use " +
"setReorderingAllowed(true) to ensure that the back stack can be " +
"restored as an atomic operation."
)
}
}
}
@Test
fun saveNonReorderingAllowedSecondTransaction() {
withUse(ActivityScenario.launch(FragmentTestActivity::class.java)) {
val fm = withActivity {
supportFragmentManager
}
val fragmentBase = StrictViewFragment()
val fragmentReplacement = StrictViewFragment()
val fragmentAboveReplacement = StrictViewFragment()
fm.beginTransaction()
.add(R.id.content, fragmentBase)
.commit()
executePendingTransactions()
fm.beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.content, fragmentReplacement)
.addToBackStack("replacement")
.commit()
executePendingTransactions()
fm.beginTransaction()
.replace(R.id.content, fragmentAboveReplacement)
.addToBackStack("above_replacement")
.commit()
executePendingTransactions()
try {
withActivity {
fm.saveBackStack("replacement")
fm.executePendingTransactions()
}
fail("executePendingTransactions() should fail with an IllegalArgumentException")
} catch (e: IllegalArgumentException) {
assertThat(e)
.hasMessageThat()
.startsWith(
"saveBackStack(\"replacement\") included FragmentTransactions must use " +
"setReorderingAllowed(true) to ensure that the back stack can be " +
"restored as an atomic operation."
)
}
}
}
@Suppress("DEPRECATION")
@Test
fun saveRetainedFragment() {
withUse(ActivityScenario.launch(FragmentTestActivity::class.java)) {
val fm = withActivity {
supportFragmentManager
}
val fragmentBase = StrictViewFragment()
val fragmentReplacement = StrictViewFragment()
fragmentReplacement.retainInstance = true
fm.beginTransaction()
.add(R.id.content, fragmentBase)
.commit()
executePendingTransactions()
fm.beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.content, fragmentReplacement)
.addToBackStack("replacement")
.commit()
executePendingTransactions()
try {
withActivity {
fm.saveBackStack("replacement")
fm.executePendingTransactions()
}
fail("executePendingTransactions() should fail with an IllegalArgumentException")
} catch (e: IllegalArgumentException) {
assertThat(e)
.hasMessageThat()
.startsWith(
"saveBackStack(\"replacement\") must not contain retained fragments. " +
"Found direct reference to retained fragment "
)
}
}
}
@Suppress("DEPRECATION")
@Test
@Ignore("b/308684873")
fun saveRetainedChildFragment() {
withUse(ActivityScenario.launch(FragmentTestActivity::class.java)) {
val fm = withActivity {
supportFragmentManager
}
val fragmentBase = StrictViewFragment()
val fragmentReplacement = StrictViewFragment()
fm.beginTransaction()
.add(R.id.content, fragmentBase)
.commit()
executePendingTransactions()
fm.beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.content, fragmentReplacement)
.addToBackStack("replacement")
.commit()
executePendingTransactions()
fragmentReplacement.childFragmentManager.beginTransaction()
.add(StrictFragment().apply { retainInstance = true }, "retained")
.commit()
executePendingTransactions(fragmentReplacement.childFragmentManager)
try {
withActivity {
fm.saveBackStack("replacement")
fm.executePendingTransactions()
}
fail("executePendingTransactions() should fail with an IllegalArgumentException")
} catch (e: IllegalArgumentException) {
assertThat(e)
.hasMessageThat()
.startsWith(
"saveBackStack(\"replacement\") must not contain retained fragments. " +
"Found retained child fragment "
)
}
}
}
@Test
fun restoreBackStack() {
withUse(ActivityScenario.launch(FragmentTestActivity::class.java)) {
val fm = withActivity {
supportFragmentManager
}
val fragmentBase = StrictViewFragment()
val fragmentReplacement = StateSaveFragment("saved", "unsaved")
fm.beginTransaction()
.add(R.id.content, fragmentBase)
.commit()
executePendingTransactions()
fm.beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.content, fragmentReplacement)
.addToBackStack("replacement")
.commit()
executePendingTransactions()
val originalViewModel = fragmentReplacement.viewModel
assertWithMessage("ViewModel should not be cleared after commit()")
.that(originalViewModel.cleared)
.isFalse()
assertThat(fm.backStackEntryCount).isEqualTo(1)
fm.saveBackStack("replacement")
executePendingTransactions()
assertWithMessage("Saved Fragments should have their state saved")
.that(fragmentReplacement.calledOnSaveInstanceState)
.isTrue()
assertThat(fm.backStackEntryCount).isEqualTo(0)
// Saved Fragments should be destroyed
assertWithMessage("Saved Fragments should be destroyed")
.that(fragmentReplacement.calledOnDestroy)
.isTrue()
// But any ViewModels should not be cleared so that they're available
// for later restoration
assertWithMessage("ViewModel should not be cleared after saveBackStack()")
.that(originalViewModel.cleared)
.isFalse()
fm.restoreBackStack("replacement")
executePendingTransactions()
val newFragmentReplacement = fm.findFragmentById(R.id.content)
assertThat(newFragmentReplacement).isInstanceOf(StateSaveFragment::class.java)
val stateSavedReplacement = newFragmentReplacement as StateSaveFragment
// Assert that restored fragment has its saved state restored
assertThat(stateSavedReplacement.savedState).isEqualTo("saved")
assertThat(stateSavedReplacement.unsavedState).isNull()
assertThat(stateSavedReplacement.viewModel).isSameInstanceAs(originalViewModel)
assertThat(fm.backStackEntryCount).isEqualTo(1)
}
}
@Test
fun restoreBackStackTwice() {
withUse(ActivityScenario.launch(FragmentTestActivity::class.java)) {
val fm = withActivity {
supportFragmentManager
}
val fragmentBase = StrictViewFragment()
val fragmentReplacement = StateSaveFragment("saved", "unsaved")
fm.beginTransaction()
.add(R.id.content, fragmentBase)
.commit()
executePendingTransactions()
fm.beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.content, fragmentReplacement)
.addToBackStack("replacement")
.commit()
executePendingTransactions()
fm.saveBackStack("replacement")
executePendingTransactions()
withActivity {
// Only the first restoreBackStack() should have state to restore
fm.restoreBackStack("replacement")
fm.restoreBackStack("replacement")
}
executePendingTransactions()
val newFragmentReplacement = fm.findFragmentById(R.id.content)
assertThat(newFragmentReplacement).isInstanceOf(StateSaveFragment::class.java)
assertThat(fm.backStackEntryCount).isEqualTo(1)
}
}
@Test
fun restoreBackStackTwoTransactions() {
withUse(ActivityScenario.launch(FragmentTestActivity::class.java)) {
val fm = withActivity {
supportFragmentManager
}
val fragmentBase = StrictViewFragment()
val fragmentReplacement = StateSaveFragment("saved", "unsaved")
val fragmentAboveReplacement = StateSaveFragment("savedAbove", "unsavedAbove")
fm.beginTransaction()
.add(R.id.content, fragmentBase)
.commit()
executePendingTransactions()
fm.beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.content, fragmentReplacement)
.addToBackStack("replacement")
.commit()
executePendingTransactions()
fm.beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.content, fragmentAboveReplacement)
.addToBackStack("above_replacement")
.commit()
executePendingTransactions()
val originalViewModel = fragmentAboveReplacement.viewModel
assertWithMessage("ViewModel should not be cleared after commit()")
.that(originalViewModel.cleared)
.isFalse()
assertThat(fm.backStackEntryCount).isEqualTo(2)
fm.saveBackStack("replacement")
executePendingTransactions()
assertWithMessage("Saved Fragments should have their state saved")
.that(fragmentReplacement.calledOnSaveInstanceState)
.isTrue()
assertWithMessage("All saved Fragments should have their state saved")
.that(fragmentAboveReplacement.calledOnSaveInstanceState)
.isTrue()
assertThat(fm.backStackEntryCount).isEqualTo(0)
// Saved Fragments should be destroyed
assertWithMessage("Saved Fragments should be destroyed")
.that(fragmentReplacement.calledOnDestroy)
.isTrue()
assertWithMessage("All saved Fragments should be destroyed")
.that(fragmentAboveReplacement.calledOnDestroy)
.isTrue()
// But any ViewModels should not be cleared so that they're available
// for later restoration
assertWithMessage("ViewModel should not be cleared after saveBackStack()")
.that(originalViewModel.cleared)
.isFalse()
fm.restoreBackStack("replacement")
executePendingTransactions()
val newFragmentReplacement = fm.findFragmentById(R.id.content)
assertThat(newFragmentReplacement).isInstanceOf(StateSaveFragment::class.java)
val stateSavedReplacement = newFragmentReplacement as StateSaveFragment
// Assert that restored fragment has its saved state restored
assertThat(stateSavedReplacement.savedState).isEqualTo("savedAbove")
assertThat(stateSavedReplacement.unsavedState).isNull()
assertThat(stateSavedReplacement.viewModel).isSameInstanceAs(originalViewModel)
assertThat(fm.backStackEntryCount).isEqualTo(2)
}
}
@Test
fun restoreBackStackAfterRecreate() {
withUse(ActivityScenario.launch(FragmentTestActivity::class.java)) {
var fm = withActivity {
supportFragmentManager
}
val fragmentBase = StrictViewFragment()
val fragmentReplacement = StateSaveFragment("saved", "unsaved")
fm.beginTransaction()
.add(R.id.content, fragmentBase)
.commit()
executePendingTransactions()
fm.beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.content, fragmentReplacement)
.addToBackStack("replacement")
.commit()
executePendingTransactions()
val originalViewModel = fragmentReplacement.viewModel
assertWithMessage("ViewModel should not be cleared after commit()")
.that(originalViewModel.cleared)
.isFalse()
assertThat(fm.backStackEntryCount).isEqualTo(1)
fm.saveBackStack("replacement")
executePendingTransactions()
assertWithMessage("Saved Fragments should have their state saved")
.that(fragmentReplacement.calledOnSaveInstanceState)
.isTrue()
assertThat(fm.backStackEntryCount).isEqualTo(0)
// Saved Fragments should be destroyed
assertWithMessage("Saved Fragments should be destroyed")
.that(fragmentReplacement.calledOnDestroy)
.isTrue()
// But any ViewModels should not be cleared so that they're available
// for later restoration
assertWithMessage("ViewModel should not be cleared after saveBackStack()")
.that(originalViewModel.cleared)
.isFalse()
// Now recreate the whole activity while the state of the back stack is saved
recreate()
fm = withActivity {
supportFragmentManager
}
fm.restoreBackStack("replacement")
executePendingTransactions()
val newFragmentReplacement = fm.findFragmentById(R.id.content)
assertThat(newFragmentReplacement).isInstanceOf(StateSaveFragment::class.java)
val stateSavedReplacement = newFragmentReplacement as StateSaveFragment
// Assert that restored fragment has its saved state restored
assertThat(stateSavedReplacement.savedState).isEqualTo("saved")
assertThat(stateSavedReplacement.unsavedState).isNull()
assertThat(stateSavedReplacement.viewModel).isSameInstanceAs(originalViewModel)
assertThat(fm.backStackEntryCount).isEqualTo(1)
}
}
@Test
fun restoreBackStackWithoutExecutePendingTransactions() {
withUse(ActivityScenario.launch(FragmentTestActivity::class.java)) {
val fm = withActivity {
supportFragmentManager
}
val fragmentBase = StrictViewFragment()
val fragmentReplacement = StateSaveFragment("saved", "unsaved")
fm.beginTransaction()
.add(R.id.content, fragmentBase)
.commit()
executePendingTransactions()
fm.beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.content, fragmentReplacement)
.addToBackStack("replacement")
.commit()
executePendingTransactions()
val originalViewModel = fragmentReplacement.viewModel
assertWithMessage("ViewModel should not be cleared after commit()")
.that(originalViewModel.cleared)
.isFalse()
assertThat(fm.backStackEntryCount).isEqualTo(1)
withActivity {
fm.saveBackStack("replacement")
// Immediately restore the back stack without calling executePendingTransactions
fm.restoreBackStack("replacement")
}
executePendingTransactions()
assertWithMessage("Saved Fragments should not go through onSaveInstanceState")
.that(fragmentReplacement.calledOnSaveInstanceState)
.isFalse()
assertWithMessage("Saved Fragments should not have been destroyed")
.that(fragmentReplacement.calledOnDestroy)
.isFalse()
assertWithMessage("ViewModel should not be cleared after saveBackStack()")
.that(originalViewModel.cleared)
.isFalse()
assertWithMessage("Fragment should still be returned by FragmentManager")
.that(fm.findFragmentById(R.id.content))
.isSameInstanceAs(fragmentReplacement)
// Assert that restored fragment has its saved state restored
assertThat(fragmentReplacement.savedState).isEqualTo("saved")
assertThat(fragmentReplacement.unsavedState).isEqualTo("unsaved")
assertThat(fragmentReplacement.viewModel).isSameInstanceAs(originalViewModel)
assertThat(fm.backStackEntryCount).isEqualTo(1)
}
}
@Test
fun clearBackStack() {
withUse(ActivityScenario.launch(FragmentTestActivity::class.java)) {
val fm = withActivity {
supportFragmentManager
}
val fragmentBase = StrictViewFragment()
val fragmentReplacement = StateSaveFragment()
fm.beginTransaction()
.add(R.id.content, fragmentBase)
.commit()
executePendingTransactions()
fm.beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.content, fragmentReplacement)
.addToBackStack("replacement")
.commit()
executePendingTransactions()
val originalViewModel = fragmentReplacement.viewModel
assertWithMessage("ViewModel should not be cleared after commit()")
.that(originalViewModel.cleared)
.isFalse()
fm.saveBackStack("replacement")
executePendingTransactions()
assertWithMessage("Saved Fragments should have their state saved")
.that(fragmentReplacement.calledOnSaveInstanceState)
.isTrue()
// Saved Fragments should be destroyed
assertWithMessage("Saved Fragments should be destroyed")
.that(fragmentReplacement.calledOnDestroy)
.isTrue()
// But any ViewModels should not be cleared so that they're available
// for later restoration
assertWithMessage("ViewModel should not be cleared after saveBackStack()")
.that(originalViewModel.cleared)
.isFalse()
fm.clearBackStack("replacement")
executePendingTransactions()
assertWithMessage("ViewModel should be cleared after the back stack is cleared")
.that(originalViewModel.cleared)
.isTrue()
}
}
@Test
@Ignore("b/308684873")
fun clearBackStackWithoutExecutePendingTransactions() {
withUse(ActivityScenario.launch(FragmentTestActivity::class.java)) {
val fm = withActivity {
supportFragmentManager
}
val fragmentBase = StrictViewFragment()
val fragmentReplacement = StateSaveFragment("saved", "unsaved")
fm.beginTransaction()
.add(R.id.content, fragmentBase)
.commit()
executePendingTransactions()
fm.beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.content, fragmentReplacement)
.addToBackStack("replacement")
.commit()
executePendingTransactions()
val originalViewModel = fragmentReplacement.viewModel
assertWithMessage("ViewModel should not be cleared after commit()")
.that(originalViewModel.cleared)
.isFalse()
assertThat(fm.backStackEntryCount).isEqualTo(1)
withActivity {
fm.saveBackStack("replacement")
// Immediately clear the back stack without calling executePendingTransactions
fm.clearBackStack("replacement")
}
executePendingTransactions()
assertWithMessage("Saved Fragments should not go through onSaveInstanceState")
.that(fragmentReplacement.calledOnSaveInstanceState)
.isFalse()
assertWithMessage("Saved Fragments should have been destroyed when cleared")
.that(fragmentReplacement.calledOnDestroy)
.isTrue()
assertWithMessage("ViewModel should be cleared after the back stack is cleared")
.that(fragmentReplacement.viewModel.cleared)
.isTrue()
// Assert that cleared fragment has been removed
assertThat(fm.backStackEntryCount).isEqualTo(0)
}
}
@Test
fun clearBackStackWithChildFragment() {
withUse(ActivityScenario.launch(FragmentTestActivity::class.java)) {
val fragmentBase = StrictViewFragment()
val fragmentParent = StrictFragment()
val fm = withActivity {
supportFragmentManager
}
fm.beginTransaction()
.add(R.id.content, fragmentBase)
.commit()
executePendingTransactions()
fm.beginTransaction()
.setReorderingAllowed(true)
.add(R.id.content, fragmentParent)
.addToBackStack("parent")
.commit()
executePendingTransactions()
fragmentParent.childFragmentManager.beginTransaction()
.add(StrictFragment(), "childFragment")
.commit()
executePendingTransactions()
val parentViewModel = ViewModelProvider(fragmentParent)[TestViewModel::class.java]
assertWithMessage("ViewModel should not be cleared after commit()")
.that(parentViewModel.cleared)
.isFalse()
val fragmentChild = fragmentParent.childFragmentManager
.findFragmentByTag("childFragment") as StrictFragment
val childViewModel = ViewModelProvider(fragmentChild)[TestViewModel::class.java]
assertWithMessage("Child ViewModel should not be cleared after commit()")
.that(childViewModel.cleared)
.isFalse()
fm.saveBackStack("parent")
executePendingTransactions()
assertWithMessage("Saved Fragments should have their state saved")
.that(fragmentParent.calledOnSaveInstanceState)
.isTrue()
assertWithMessage("Saved Child Fragments should have their state saved")
.that(fragmentChild.calledOnSaveInstanceState)
.isTrue()
// Saved Fragments should be destroyed
assertWithMessage("Saved Fragments should be destroyed")
.that(fragmentParent.calledOnDestroy)
.isTrue()
assertWithMessage("Saved Child Fragments should be destroyed")
.that(fragmentChild.calledOnDestroy)
.isTrue()
// But any ViewModels should not be cleared so that they're available
// for later restoration
assertWithMessage("ViewModel should not be cleared after saveBackStack()")
.that(parentViewModel.cleared)
.isFalse()
assertWithMessage("Child ViewModel should not be cleared after saveBackStack()")
.that(childViewModel.cleared)
.isFalse()
fm.clearBackStack("parent")
executePendingTransactions()
assertWithMessage("ViewModel should be cleared after the back stack is cleared")
.that(parentViewModel.cleared)
.isTrue()
assertWithMessage("Child ViewModel should be cleared after the back stack is cleared")
.that(childViewModel.cleared)
.isTrue()
}
}
@Test
fun clearBackStackWithMultiChildFragment() {
withUse(ActivityScenario.launch(FragmentTestActivity::class.java)) {
val fragmentBase = StrictViewFragment()
val fragmentParent = StrictFragment()
val fm = withActivity {
supportFragmentManager
}
fm.beginTransaction()
.add(R.id.content, fragmentBase)
.commit()
executePendingTransactions()
fm.beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.content, fragmentParent)
.addToBackStack("parent")
.commit()
executePendingTransactions()
val parentViewModel = ViewModelProvider(fragmentParent).get(TestViewModel::class.java)
assertWithMessage("ViewModel should not be cleared after commit()")
.that(parentViewModel.cleared)
.isFalse()
// add child 1
fragmentParent.childFragmentManager.beginTransaction()
.add(StrictFragment(), "childFragment1")
.commit()
executePendingTransactions()
val fragmentChild1 = fragmentParent.childFragmentManager
.findFragmentByTag("childFragment1") as StrictFragment
val childViewModel1 = ViewModelProvider(fragmentChild1)[TestViewModel::class.java]
assertWithMessage("Child ViewModel should not be cleared after commit()")
.that(childViewModel1.cleared)
.isFalse()
// add child 2
fragmentParent.childFragmentManager.beginTransaction()
.add(StrictFragment(), "childFragment2")
.commit()
executePendingTransactions()
val fragmentChild2 = fragmentParent.childFragmentManager
.findFragmentByTag("childFragment2") as StrictFragment
val childViewModel2 = ViewModelProvider(fragmentChild2)[TestViewModel::class.java]
assertWithMessage("Child ViewModel should not be cleared after commit()")
.that(childViewModel2.cleared)
.isFalse()
fm.saveBackStack("parent")
executePendingTransactions()
assertWithMessage("Saved Fragments should have their state saved")
.that(fragmentParent.calledOnSaveInstanceState)
.isTrue()
assertWithMessage("Saved Child Fragments should have their state saved")
.that(fragmentChild1.calledOnSaveInstanceState)
.isTrue()
assertWithMessage("Saved Child Fragments should have their state saved")
.that(fragmentChild2.calledOnSaveInstanceState)
.isTrue()
// Saved Fragments should be destroyed
assertWithMessage("Saved Fragments should be destroyed")
.that(fragmentParent.calledOnDestroy)
.isTrue()
assertWithMessage("Saved Fragments should be destroyed")
.that(fragmentChild1.calledOnDestroy)
.isTrue()
assertWithMessage("Saved Fragments should be destroyed")
.that(fragmentChild2.calledOnDestroy)
.isTrue()
// But any ViewModels should not be cleared so that they're available
// for later restoration
assertWithMessage("ViewModel should not be cleared after saveBackStack()")
.that(parentViewModel.cleared)
.isFalse()
assertWithMessage("ViewModel should not be cleared after saveBackStack()")
.that(childViewModel1.cleared)
.isFalse()
assertWithMessage("Child ViewModel should not be cleared after saveBackStack()")
.that(childViewModel2.cleared)
.isFalse()
fm.clearBackStack("parent")
executePendingTransactions()
assertWithMessage("ViewModel should be cleared after the back stack is cleared")
.that(parentViewModel.cleared)
.isTrue()
assertWithMessage("Child ViewModel should be cleared after the back stack is cleared")
.that(childViewModel1.cleared)
.isTrue()
assertWithMessage("Child ViewModel should be cleared after the back stack is cleared")
.that(childViewModel2.cleared)
.isTrue()
}
}
@Test
fun clearBackStackWithGrandchildFragments() {
withUse(ActivityScenario.launch(FragmentTestActivity::class.java)) {
val fragmentBase = StrictViewFragment()
val grandParent = StrictFragment()
val fm = withActivity {
supportFragmentManager
}
fm.beginTransaction()
.add(R.id.content, fragmentBase)
.commit()
executePendingTransactions()
fm.beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.content, grandParent)
.addToBackStack("grandparent")
.commit()
executePendingTransactions()
val grandParentViewModel = ViewModelProvider(grandParent)[TestViewModel::class.java]
assertWithMessage("ViewModel should not be cleared after commit()")
.that(grandParentViewModel.cleared)
.isFalse()
// add parent
grandParent.childFragmentManager.beginTransaction()
.add(StrictFragment(), "parent")
.commit()
executePendingTransactions()
val parent = grandParent.childFragmentManager
.findFragmentByTag("parent") as StrictFragment
val parentViewModel = ViewModelProvider(parent)[TestViewModel::class.java]
assertWithMessage("ViewModel should not be cleared after commit()")
.that(parentViewModel.cleared)
.isFalse()
// add grandchild
parent.childFragmentManager.beginTransaction()
.add(StrictFragment(), "grandchild")
.commit()
executePendingTransactions()
val grandChild = parent.childFragmentManager
.findFragmentByTag("grandchild") as StrictFragment
val grandChildViewModel = ViewModelProvider(grandChild)[TestViewModel::class.java]
assertWithMessage("Child ViewModel should not be cleared after commit()")
.that(grandChildViewModel.cleared)
.isFalse()
fm.saveBackStack("grandparent")
executePendingTransactions()
assertWithMessage("Saved Fragments should have their state saved")
.that(grandParent.calledOnSaveInstanceState)
.isTrue()
assertWithMessage("Saved Child Fragments should have their state saved")
.that(parent.calledOnSaveInstanceState)
.isTrue()
assertWithMessage("Saved Child Fragments should have their state saved")
.that(grandChild.calledOnSaveInstanceState)
.isTrue()
// Saved Fragments should be destroyed
assertWithMessage("Saved Fragments should be destroyed")
.that(grandParent.calledOnDestroy)
.isTrue()
assertWithMessage("Saved Child Fragments should be destroyed")
.that(parent.calledOnDestroy)
.isTrue()
assertWithMessage("Saved Child Fragments should be destroyed")
.that(grandChild.calledOnDestroy)
.isTrue()
// But any ViewModels should not be cleared so that they're available
// for later restoration
assertWithMessage("ViewModel should not be cleared after saveBackStack()")
.that(grandParentViewModel.cleared)
.isFalse()
assertWithMessage("ViewModel should not be cleared after saveBackStack()")
.that(parentViewModel.cleared)
.isFalse()
assertWithMessage("Child ViewModel should not be cleared after saveBackStack()")
.that(grandChildViewModel.cleared)
.isFalse()
fm.clearBackStack("grandparent")
executePendingTransactions()
assertWithMessage("ViewModel should be cleared after the back stack is cleared")
.that(grandParentViewModel.cleared)
.isTrue()
assertWithMessage("ViewModel should be cleared after the back stack is cleared")
.that(parentViewModel.cleared)
.isTrue()
assertWithMessage("Child ViewModel should be cleared after the back stack is cleared")
.that(grandChildViewModel.cleared)
.isTrue()
}
}
@Test
fun resumeClearsFragmentStoreSavedState() {
withUse(ActivityScenario.launch(FragmentTestActivity::class.java)) {
val fm = withActivity {
supportFragmentManager
}
val fragmentBase = StrictViewFragment()
val fragmentReplacement = StateSaveFragment()
val fragmentReplacementChild = StateSaveFragment()
fm.beginTransaction()
.add(R.id.content, fragmentBase)
.commit()
executePendingTransactions()
fm.beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.content, fragmentReplacement)
.addToBackStack("replacement")
.commit()
executePendingTransactions()
fragmentReplacement.childFragmentManager.beginTransaction()
.add(fragmentReplacementChild, "replacementChild")
.commit()
executePendingTransactions(fragmentReplacement.childFragmentManager)
// stop activity and save fragments
moveToState(Lifecycle.State.CREATED)
executePendingTransactions()
// states should be stored in fragmentStore
assertThat(fm.fragmentStore.getSavedState(fragmentReplacement.mWho))
.isNotNull()
assertThat(fragmentReplacement.childFragmentManager.fragmentStore
.getSavedState(fragmentReplacementChild.mWho)
).isNotNull()
// resume activity and restore fragments
moveToState(Lifecycle.State.RESUMED)
executePendingTransactions()
// states should be cleared from fragmentStore
assertThat(fm.fragmentStore.getSavedState(fragmentReplacement.mWho))
.isNull()
assertThat(fragmentReplacement.childFragmentManager.fragmentStore
.getSavedState(fragmentReplacementChild.mWho)
).isNull()
}
}
}