Fragment grab bag!

* Fragment#isStateSaved: report whether a given fragment has been
  through state save within its given FragmentManager. This will
  additionally echo the state of its child FragmentManager, indicating
  whether it is safe to perform transactions without losing state.

* Fragment#setArguments can now be called at any time before a
  fragment's state is saved, not just before an internal index is
  assigned. This fixes an issue with assigning arguments during
  fragment inflation in some code paths.

* FragmentTransaction#postOnCommit: Add a Runnable to a
  FragmentTransaction that will be executed once the operations in the
  transaction have been fully executed. Transaction optimization may
  mean that other transaction ops could run before this is executed,
  or that specific operations from this transaction were optimized
  out. (e.g. a fragment in this transaction was added, but then
  removed in a subsequent transaction before pending operations were
  executed.) This may not be used for transactions added to the back
  stack, as we cannot persist the runnable for later to guarantee
  running it when the transaction is reversed.

Test: CTS
Change-Id: I4eb90588135e30f287c6487aa62ebda231f3dad3
This commit is contained in:
Adam Powell
2017-01-31 14:54:07 -08:00
parent 2591ce4d9f
commit adfd62c484
7 changed files with 77 additions and 5 deletions

View File

@@ -4516,6 +4516,7 @@ package android.app {
method public final boolean isInLayout();
method public final boolean isRemoving();
method public final boolean isResumed();
method public final boolean isStateSaved();
method public final boolean isVisible();
method public void onActivityCreated(android.os.Bundle);
method public void onActivityResult(int, int, android.content.Intent);
@@ -4754,6 +4755,7 @@ package android.app {
method public abstract android.app.FragmentTransaction hide(android.app.Fragment);
method public abstract boolean isAddToBackStackAllowed();
method public abstract boolean isEmpty();
method public abstract android.app.FragmentTransaction postOnCommit(java.lang.Runnable);
method public abstract android.app.FragmentTransaction remove(android.app.Fragment);
method public abstract android.app.FragmentTransaction replace(int, android.app.Fragment);
method public abstract android.app.FragmentTransaction replace(int, android.app.Fragment, java.lang.String);

View File

@@ -4676,6 +4676,7 @@ package android.app {
method public final boolean isInLayout();
method public final boolean isRemoving();
method public final boolean isResumed();
method public final boolean isStateSaved();
method public final boolean isVisible();
method public void onActivityCreated(android.os.Bundle);
method public void onActivityResult(int, int, android.content.Intent);
@@ -4914,6 +4915,7 @@ package android.app {
method public abstract android.app.FragmentTransaction hide(android.app.Fragment);
method public abstract boolean isAddToBackStackAllowed();
method public abstract boolean isEmpty();
method public abstract android.app.FragmentTransaction postOnCommit(java.lang.Runnable);
method public abstract android.app.FragmentTransaction remove(android.app.Fragment);
method public abstract android.app.FragmentTransaction replace(int, android.app.Fragment);
method public abstract android.app.FragmentTransaction replace(int, android.app.Fragment, java.lang.String);

View File

@@ -4526,6 +4526,7 @@ package android.app {
method public final boolean isInLayout();
method public final boolean isRemoving();
method public final boolean isResumed();
method public final boolean isStateSaved();
method public final boolean isVisible();
method public void onActivityCreated(android.os.Bundle);
method public void onActivityResult(int, int, android.content.Intent);
@@ -4764,6 +4765,7 @@ package android.app {
method public abstract android.app.FragmentTransaction hide(android.app.Fragment);
method public abstract boolean isAddToBackStackAllowed();
method public abstract boolean isEmpty();
method public abstract android.app.FragmentTransaction postOnCommit(java.lang.Runnable);
method public abstract android.app.FragmentTransaction remove(android.app.Fragment);
method public abstract android.app.FragmentTransaction replace(int, android.app.Fragment);
method public abstract android.app.FragmentTransaction replace(int, android.app.Fragment, java.lang.String);

View File

@@ -220,6 +220,8 @@ final class BackStackRecord extends FragmentTransaction implements
int mIndex = -1;
boolean mAllowOptimization;
ArrayList<Runnable> mCommitRunnables;
int mBreadCrumbTitleRes;
CharSequence mBreadCrumbTitleText;
int mBreadCrumbShortTitleRes;
@@ -620,6 +622,28 @@ final class BackStackRecord extends FragmentTransaction implements
}
}
@Override
public FragmentTransaction postOnCommit(Runnable runnable) {
if (runnable == null) {
throw new IllegalArgumentException("runnable cannot be null");
}
disallowAddToBackStack();
if (mCommitRunnables == null) {
mCommitRunnables = new ArrayList<>();
}
mCommitRunnables.add(runnable);
return this;
}
public void runOnCommitRunnables() {
if (mCommitRunnables != null) {
for (int i = 0, N = mCommitRunnables.size(); i < N; i++) {
mCommitRunnables.get(i).run();
}
mCommitRunnables = null;
}
}
public int commit() {
return commitInternal(false);
}

View File

@@ -714,14 +714,20 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
}
/**
* Supply the construction arguments for this fragment. This can only
* be called before the fragment has been attached to its activity; that
* is, you should call it immediately after constructing the fragment. The
* arguments supplied here will be retained across fragment destroy and
* Supply the construction arguments for this fragment.
* The arguments supplied here will be retained across fragment destroy and
* creation.
*
* <p>This method cannot be called if the fragment is added to a FragmentManager and
* if {@link #isStateSaved()} would return true. Prior to {@link Build.VERSION_CODES#O},
* this method may only be called if the fragment has not yet been added to a FragmentManager.
* </p>
*/
public void setArguments(Bundle args) {
if (mIndex >= 0) {
// The isStateSaved requirement below was only added in Android O and is compatible
// because it loosens previous requirements rather than making them more strict.
// See method javadoc.
if (mIndex >= 0 && isStateSaved()) {
throw new IllegalStateException("Fragment already active");
}
mArguments = args;
@@ -734,6 +740,21 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
return mArguments;
}
/**
* Returns true if this fragment is added and its state has already been saved
* by its host. Any operations that would change saved state should not be performed
* if this method returns true, and some operations such as {@link #setArguments(Bundle)}
* will fail.
*
* @return true if this fragment's state has already been saved by its host
*/
public final boolean isStateSaved() {
if (mFragmentManager == null) {
return false;
}
return mFragmentManager.isStateSaved();
}
/**
* Set the initial saved state that this Fragment should restore itself
* from when first being constructed, as returned by

View File

@@ -1787,6 +1787,10 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate
}
}
public boolean isStateSaved() {
return mStateSaved;
}
/**
* Adds an action to the queue of pending actions.
*
@@ -2108,6 +2112,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate
freeBackStackIndex(record.mIndex);
record.mIndex = -1;
}
record.runOnCommitRunnables();
}
if (addToBackStack) {

View File

@@ -303,6 +303,22 @@ public abstract class FragmentTransaction {
*/
public abstract FragmentTransaction setAllowOptimization(boolean allowOptimization);
/**
* Add a Runnable to this transaction that will be run after this transaction has
* been committed. If fragment transactions are {@link #setAllowOptimization(boolean) optimized}
* this may be after other subsequent fragment operations have also taken place, or operations
* in this transaction may have been optimized out due to the presence of a subsequent
* fragment transaction in the batch.
*
* <p><code>postOnCommit</code> may not be used with transactions
* {@link #addToBackStack(String) added to the back stack} as Runnables cannot be persisted
* with back stack state.</p>
*
* @param runnable Runnable to add
* @return this FragmentTransaction
*/
public abstract FragmentTransaction postOnCommit(Runnable runnable);
/**
* Schedules a commit of this transaction. The commit does
* not happen immediately; it will be scheduled as work on the main thread