Merge "Fix direct transition from VIEW_CREATED to CREATED" into androidx-master-dev
diff --git a/fragment/fragment/src/androidTest/AndroidManifest.xml b/fragment/fragment/src/androidTest/AndroidManifest.xml
index 5bac34a..97ee537 100644
--- a/fragment/fragment/src/androidTest/AndroidManifest.xml
+++ b/fragment/fragment/src/androidTest/AndroidManifest.xml
@@ -48,6 +48,7 @@
<activity android:name="androidx.fragment.app.FragmentSavedStateActivity" />
<activity android:name="androidx.fragment.app.FragmentFinishEarlyTestActivity" />
<activity android:name="androidx.fragment.app.SimpleContainerActivity" />
+ <activity android:name="androidx.fragment.app.ReplaceInCreateActivity" />
<activity android:name="androidx.fragment.app.DialogActivity"
android:theme="@style/DialogTheme"/>
<activity android:name="androidx.fragment.app.TestAppCompatActivity"
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentReplaceTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentReplaceTest.kt
index 4c155a7..f10ddfc 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentReplaceTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentReplaceTest.kt
@@ -15,60 +15,121 @@
*/
package androidx.fragment.app
+import android.os.Bundle
import android.view.View
import androidx.fragment.app.test.FragmentTestActivity
import androidx.fragment.test.R
+import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
+import androidx.test.filters.LargeTest
+import androidx.testutils.withActivity
import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
/**
- * Test to prevent regressions in SupportFragmentManager fragment replace method. See b/24693644
+ * Test to prevent regressions in SupportFragmentManager fragment replace method.
*/
@RunWith(AndroidJUnit4::class)
-@MediumTest
+@LargeTest
class FragmentReplaceTest {
- @Suppress("DEPRECATION")
- @get:Rule
- val activityRule = androidx.test.rule.ActivityTestRule(FragmentTestActivity::class.java)
@Test
fun testReplaceFragment() {
- val activity = activityRule.activity
- val fm = activity.supportFragmentManager
+ with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+ val fm = withActivity { supportFragmentManager }
- fm.beginTransaction()
- .add(R.id.content, StrictViewFragment(R.layout.fragment_a))
- .addToBackStack(null)
- .commit()
- executePendingTransactions(fm)
- assertThat(activity.findViewById<View>(R.id.textA)).isNotNull()
- assertThat(activity.findViewById<View>(R.id.textB)).isNull()
- assertThat(activity.findViewById<View>(R.id.textC)).isNull()
+ fm.beginTransaction()
+ .add(R.id.content, StrictViewFragment(R.layout.fragment_a))
+ .addToBackStack(null)
+ .commit()
+ executePendingTransactions()
+ withActivity {
+ assertThat(findViewById<View>(R.id.textA)).isNotNull()
+ assertThat(findViewById<View>(R.id.textB)).isNull()
+ assertThat(findViewById<View>(R.id.textC)).isNull()
+ }
- fm.beginTransaction()
- .add(R.id.content, StrictViewFragment(R.layout.fragment_b))
- .addToBackStack(null)
- .commit()
- executePendingTransactions(fm)
- assertThat(activity.findViewById<View>(R.id.textA)).isNotNull()
- assertThat(activity.findViewById<View>(R.id.textB)).isNotNull()
- assertThat(activity.findViewById<View>(R.id.textC)).isNull()
+ fm.beginTransaction()
+ .add(R.id.content, StrictViewFragment(R.layout.fragment_b))
+ .addToBackStack(null)
+ .commit()
+ executePendingTransactions()
+ withActivity {
+ assertThat(findViewById<View>(R.id.textA)).isNotNull()
+ assertThat(findViewById<View>(R.id.textB)).isNotNull()
+ assertThat(findViewById<View>(R.id.textC)).isNull()
+ }
- activity.supportFragmentManager.beginTransaction()
- .replace(R.id.content, StrictViewFragment(R.layout.fragment_c))
- .addToBackStack(null)
- .commit()
- executePendingTransactions(fm)
- assertThat(activity.findViewById<View>(R.id.textA)).isNull()
- assertThat(activity.findViewById<View>(R.id.textB)).isNull()
- assertThat(activity.findViewById<View>(R.id.textC)).isNotNull()
+ fm.beginTransaction()
+ .replace(R.id.content, StrictViewFragment(R.layout.fragment_c))
+ .addToBackStack(null)
+ .commit()
+ executePendingTransactions()
+ withActivity {
+ assertThat(findViewById<View>(R.id.textA)).isNull()
+ assertThat(findViewById<View>(R.id.textB)).isNull()
+ assertThat(findViewById<View>(R.id.textC)).isNotNull()
+ }
+ }
}
- private fun executePendingTransactions(fm: FragmentManager) {
- activityRule.runOnUiThread { fm.executePendingTransactions() }
+ @Test
+ fun testReplaceFragmentInOnCreate() {
+ with(ActivityScenario.launch(ReplaceInCreateActivity::class.java)) {
+ val replaceInCreateFragment = withActivity { this.replaceInCreateFragment }
+
+ assertThat(replaceInCreateFragment.isAdded)
+ .isFalse()
+ withActivity {
+ assertThat(findViewById<View>(R.id.textA)).isNull()
+ assertThat(findViewById<View>(R.id.textB)).isNotNull()
+ }
+ }
+ }
+}
+
+class ReplaceInCreateActivity : FragmentActivity(R.layout.activity_content) {
+ private val parentFragment: ParentFragment
+ get() = supportFragmentManager.findFragmentById(R.id.content) as ParentFragment
+ val replaceInCreateFragment: ReplaceInCreateFragment
+ get() = parentFragment.replaceInCreateFragment
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if (savedInstanceState == null) {
+ // This issue only appears for child fragments
+ // so add parent fragment that contains the ReplaceInCreateFragment
+ supportFragmentManager.beginTransaction()
+ .add(R.id.content, ParentFragment())
+ .setReorderingAllowed(true)
+ .commit()
+ }
+ }
+
+ class ParentFragment : StrictViewFragment(R.layout.simple_container) {
+ lateinit var replaceInCreateFragment: ReplaceInCreateFragment
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if (savedInstanceState == null) {
+ replaceInCreateFragment = ReplaceInCreateFragment()
+ childFragmentManager.beginTransaction()
+ .add(R.id.fragmentContainer, replaceInCreateFragment)
+ .setReorderingAllowed(true)
+ .commit()
+ }
+ }
+ }
+}
+
+class ReplaceInCreateFragment : StrictViewFragment(R.layout.fragment_a) {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ parentFragmentManager.beginTransaction()
+ .replace(R.id.fragmentContainer, StrictViewFragment(R.layout.fragment_b))
+ .setReorderingAllowed(true)
+ .commit()
}
}
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt
index 0720e07..256a07b 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt
@@ -51,6 +51,7 @@
checkGetActivity()
checkActivityNotDestroyed()
checkState("onViewCreated", State.CREATED)
+ currentState = State.VIEW_CREATED
onViewCreatedCalled = true
}
@@ -78,7 +79,8 @@
.isNotNull()
}
checkGetActivity()
- checkState("onDestroyView", State.CREATED)
+ checkState("onDestroyView", State.CREATED, State.VIEW_CREATED, State.ACTIVITY_CREATED)
+ currentState = State.CREATED
onDestroyViewCalled = true
}
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java
index 360beab..4446391 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java
@@ -247,6 +247,10 @@
if (mFragment.mDeferStart && mFragment.mState < Fragment.STARTED) {
maxState = Math.min(maxState, Fragment.ACTIVITY_CREATED);
}
+ if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+ Log.v(FragmentManager.TAG, "computeExpectedState() of " + maxState + " for "
+ + mFragment);
+ }
return maxState;
}
@@ -335,10 +339,11 @@
mFragment.mState = Fragment.AWAITING_EXIT_EFFECTS;
break;
case Fragment.VIEW_CREATED:
- destroyFragmentView();
+ mFragment.mInLayout = false;
mFragment.mState = Fragment.VIEW_CREATED;
break;
case Fragment.CREATED:
+ destroyFragmentView();
mFragment.mState = Fragment.CREATED;
break;
case Fragment.ATTACHED:
@@ -725,6 +730,12 @@
if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
Log.d(TAG, "movefrom CREATE_VIEW: " + mFragment);
}
+ // In cases where we never got up to AWAITING_EXIT_EFFECTS, we
+ // need to manually remove the view from the container to reverse
+ // what we did in createView()
+ if (mFragment.mContainer != null && mFragment.mView != null) {
+ mFragment.mContainer.removeView(mFragment.mView);
+ }
mFragment.performDestroyView();
mDispatcher.dispatchOnFragmentViewDestroyed(mFragment, false);
mFragment.mContainer = null;
diff --git a/testutils/testutils-runtime/src/main/java/androidx/fragment/app/StrictFragment.kt b/testutils/testutils-runtime/src/main/java/androidx/fragment/app/StrictFragment.kt
index b7feaa3..e52994e 100644
--- a/testutils/testutils-runtime/src/main/java/androidx/fragment/app/StrictFragment.kt
+++ b/testutils/testutils-runtime/src/main/java/androidx/fragment/app/StrictFragment.kt
@@ -127,7 +127,7 @@
super.onActivityCreated(savedInstanceState)
checkActivityNotDestroyed()
calledOnActivityCreated = true
- checkState("onActivityCreated", State.ATTACHED, State.CREATED)
+ checkState("onActivityCreated", State.ATTACHED, State.CREATED, State.VIEW_CREATED)
val fromState = currentState
currentState = State.ACTIVITY_CREATED
onStateChanged(fromState)
@@ -198,6 +198,7 @@
DETACHED,
ATTACHED,
CREATED,
+ VIEW_CREATED,
ACTIVITY_CREATED,
STARTED,
RESUMED