FragmentTransaction.commitNow, framework edition
Offer commitNow and commitNowAllowingStateLoss methods on Fragment for
use by encapsulated components using fragments as implementation
details. This can help prevent unexpected ordering side effects at the
app level when a call to a library method wants to commit and
immediately initialize a fragment as an implementation detail.
Note that this change still does not permit reentrant FragmentManager
operations. It is still an error to add/remove/change fragments in the
same FragmentManager while a fragment transaction is being executed.
Have the commonly used ViewPager adapters use commitNow instead of
executePendingTransactions.
Change-Id: Ia37a871234a287423063f0c2c3e4c93d69116cad
(cherry picked from commit f6b30662f87f7339d0d3946dcf71e930c2fead9b)
diff --git a/api/current.txt b/api/current.txt
index 6406f57..7779ec1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4604,6 +4604,8 @@
method public abstract android.app.FragmentTransaction attach(android.app.Fragment);
method public abstract int commit();
method public abstract int commitAllowingStateLoss();
+ method public abstract void commitNow();
+ method public abstract void commitNowAllowingStateLoss();
method public abstract android.app.FragmentTransaction detach(android.app.Fragment);
method public abstract android.app.FragmentTransaction disallowAddToBackStack();
method public abstract android.app.FragmentTransaction hide(android.app.Fragment);
diff --git a/api/system-current.txt b/api/system-current.txt
index ed094f9..1f9d291 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4737,6 +4737,8 @@
method public abstract android.app.FragmentTransaction attach(android.app.Fragment);
method public abstract int commit();
method public abstract int commitAllowingStateLoss();
+ method public abstract void commitNow();
+ method public abstract void commitNowAllowingStateLoss();
method public abstract android.app.FragmentTransaction detach(android.app.Fragment);
method public abstract android.app.FragmentTransaction disallowAddToBackStack();
method public abstract android.app.FragmentTransaction hide(android.app.Fragment);
diff --git a/api/test-current.txt b/api/test-current.txt
index 417107d..6e6f99a 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -4604,6 +4604,8 @@
method public abstract android.app.FragmentTransaction attach(android.app.Fragment);
method public abstract int commit();
method public abstract int commitAllowingStateLoss();
+ method public abstract void commitNow();
+ method public abstract void commitNowAllowingStateLoss();
method public abstract android.app.FragmentTransaction detach(android.app.Fragment);
method public abstract android.app.FragmentTransaction disallowAddToBackStack();
method public abstract android.app.FragmentTransaction hide(android.app.Fragment);
diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java
index a147cc8..eb4b13e 100644
--- a/core/java/android/app/BackStackRecord.java
+++ b/core/java/android/app/BackStackRecord.java
@@ -667,6 +667,18 @@
return commitInternal(true);
}
+ @Override
+ public void commitNow() {
+ disallowAddToBackStack();
+ mManager.execSingleAction(this, false);
+ }
+
+ @Override
+ public void commitNowAllowingStateLoss() {
+ disallowAddToBackStack();
+ mManager.execSingleAction(this, true);
+ }
+
int commitInternal(boolean allowStateLoss) {
if (mCommitted) {
throw new IllegalStateException("commit already called");
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 32fec84..8b5dbae 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -1503,6 +1503,26 @@
}
}
+ public void execSingleAction(Runnable action, boolean allowStateLoss) {
+ if (mExecutingActions) {
+ throw new IllegalStateException("FragmentManager is already executing transactions");
+ }
+
+ if (Looper.myLooper() != mHost.getHandler().getLooper()) {
+ throw new IllegalStateException("Must be called from main thread of fragment host");
+ }
+
+ if (allowStateLoss) {
+ checkStateLoss();
+ }
+
+ mExecutingActions = true;
+ action.run();
+ mExecutingActions = false;
+
+ doPendingDeferredStart();
+ }
+
/**
* Only call from main thread!
*/
@@ -1543,6 +1563,12 @@
didSomething = true;
}
+ doPendingDeferredStart();
+
+ return didSomething;
+ }
+
+ void doPendingDeferredStart() {
if (mHavePendingDeferredStart) {
boolean loadersRunning = false;
for (int i=0; i<mActive.size(); i++) {
@@ -1556,7 +1582,6 @@
startPendingDeferredFragments();
}
}
- return didSomething;
}
void reportBackStackChanged() {
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
index 0249cb2..e435580 100644
--- a/core/java/android/app/FragmentTransaction.java
+++ b/core/java/android/app/FragmentTransaction.java
@@ -284,4 +284,45 @@
* to change unexpectedly on the user.
*/
public abstract int commitAllowingStateLoss();
+
+ /**
+ * Commits this transaction synchronously. Any added fragments will be
+ * initialized and brought completely to the lifecycle state of their host
+ * and any removed fragments will be torn down accordingly before this
+ * call returns. Committing a transaction in this way allows fragments
+ * to be added as dedicated, encapsulated components that monitor the
+ * lifecycle state of their host while providing firmer ordering guarantees
+ * around when those fragments are fully initialized and ready. Fragments
+ * that manage views will have those views created and attached.
+ *
+ * <p>Calling <code>commitNow</code> is preferable to calling
+ * {@link #commit()} followed by {@link FragmentManager#executePendingTransactions()}
+ * as the latter will have the side effect of attempting to commit <em>all</em>
+ * currently pending transactions whether that is the desired behavior
+ * or not.</p>
+ *
+ * <p>Transactions committed in this way may not be added to the
+ * FragmentManager's back stack, as doing so would break other expected
+ * ordering guarantees for other asynchronously committed transactions.
+ * This method will throw {@link IllegalStateException} if the transaction
+ * previously requested to be added to the back stack with
+ * {@link #addToBackStack(String)}.</p>
+ *
+ * <p class="note">A transaction can only be committed with this method
+ * prior to its containing activity saving its state. If the commit is
+ * attempted after that point, an exception will be thrown. This is
+ * because the state after the commit can be lost if the activity needs to
+ * be restored from its state. See {@link #commitAllowingStateLoss()} for
+ * situations where it may be okay to lose the commit.</p>
+ */
+ public abstract void commitNow();
+
+ /**
+ * Like {@link #commitNow} but allows the commit to be executed after an
+ * activity's state is saved. This is dangerous because the commit can
+ * be lost if the activity needs to later be restored from its state, so
+ * this should only be used for cases where it is okay for the UI state
+ * to change unexpectedly on the user.
+ */
+ public abstract void commitNowAllowingStateLoss();
}