[go: nahoru, domu]

Ensure onInflate is always called before Fragment is attached

With the changes in aosp/1183079 fragments added with the <fragment> tag
but not in a layout were allowed to move to CREATED so that they could
be cleaned up appropriately. As a side affect, fragments not in layout
could now be CREATED by the time they get the onInflate() callback. This
could cause unexpected behavior for users who expect onInflate() to
always be called before the fragment is ATTACHED.

This change ensures that fragments added with the <fragment> tag, but
not in a layout do not move to created until its fragmentManager is at
least ACTIVITY_CREATED. This ensures we continue to call onInflate()
before the fragment is ATTACHED, but also that inflated fragments are
cleaned up appropriately.

Test: Modified StrictFragment
Fixes: 147462530
Change-Id: Id9935535d8394cba22d201c8bc6fd63638d0a1d6
(cherry picked from commit 0bc25bbc8c161cdadfd1217869d8294f8ad87700)
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
index 595221d..ca648bb 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
@@ -2550,6 +2550,12 @@
     }
 
     void noteStateNotSaved() {
+        // A fragment added via the <fragment> tag can have noteStateNotSaved() called
+        // by its parent fragment before attachController() has been called. In this case,
+        // we should early return as the state not being saved is the default.
+        if (mHost == null) {
+            return;
+        }
         mStateSaved = false;
         mStopped = false;
         for (Fragment fragment : mFragmentStore.getFragments()) {
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 56e2cab..943b71d 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java
@@ -161,9 +161,18 @@
                 // actually added to the layout (mInLayout).
                 maxState = Math.max(mFragmentManagerState, Fragment.CREATED);
             } else {
-                // But don't allow their state to progress upward beyond CREATED
-                // if they're not in a layout
-                maxState = Math.min(maxState, Fragment.CREATED);
+                if (mFragmentManagerState < Fragment.ACTIVITY_CREATED) {
+                    // But while they are not in the layout, don't allow their
+                    // state to progress upward until the FragmentManager state
+                    // is at least ACTIVITY_CREATED. This ensures they get the onInflate()
+                    // callback before being attached or created.
+                    maxState = Math.min(maxState, mFragment.mState);
+                } else {
+                    // Once the FragmentManager state is at least ACTIVITY_CREATED
+                    // their state can progress up to CREATED as we assume that
+                    // they are not ever going to be in layout
+                    maxState = Math.min(maxState, Fragment.CREATED);
+                }
             }
         }
         // Fragments that are not currently added will sit in the CREATED state.
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 2f2a63f..1890bcc 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
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.os.Bundle
+import android.util.AttributeSet
 import androidx.annotation.LayoutRes
 
 /**
@@ -82,6 +83,12 @@
         calledOnAttachFragment = true
     }
 
+    override fun onInflate(context: Context, attrs: AttributeSet, savedInstanceState: Bundle?) {
+        super.onInflate(context, attrs, savedInstanceState)
+        checkActivityNotDestroyed()
+        checkState("onInflate", State.DETACHED)
+    }
+
     override fun onAttach(context: Context) {
         super.onAttach(context)
         calledOnAttach = true