Years ago, I ran into a problem in one of my apps when I tried to commit a FragmentTransaction
inside my onActivityResult()
callback. Googling around, I found this question and answer, which say
At the time that
onActivityResult()
is called, the activity/fragment's state may not yet have been restored, and therefore any transactions that happen during this time will be lost as a result.
I wound up adapting the solution recommended in the same answer for my app, and life was good. However, recent experimentation shows that perhaps things have changed, and it may now be safe to commit a FragmentTransaction
from inside onActivityResult()
.
The documentation for (support v4) FragmentManager.beginTransaction()
defines the safe window for transactions as:
Note: A fragment transaction can only be created/committed prior to an activity saving its state. If you try to commit a transaction after
FragmentActivity.onSaveInstanceState()
(and prior to a followingFragmentActivity.onStart
orFragmentActivity.onResume()
, you will get an error.
Reading the documentation for onActivityResult()
, I see
You will receive this call immediately before
onResume()
when your activity is re-starting.
This leads me to believe that it should be safe to execute these transactions in onActivityResult()
, as onStart()
will have already been called, putting me inside the safe window.
I made an app to test this out, and I'm successfully seeing dialog fragments that I create and commit inside onActivityResult()
. I had the same app also log the activity lifecycle callbacks so I could inspect their order, and I see onStart()
, then onRestoreInstanceState()
, and then onActivityResult()
every time.
Am I missing something? Or has the framework changed and onActivityResult()
is now guaranteed to be a safe place for fragment transactions? Does this behavior vary by API level?
I found another question and answer that seems to read the documentation the same way I have, but both are over a year old and neither specifically refers to onActivityResult()
as a safe place for transactions.
That means when you call FragmentTransaction#commit() after onSaveInstanceState() is called, the transaction won't be remembered because it was never recorded as part of the Activity's state in the first place. From the user's point of view, the transaction will appear to be lost, resulting in accidental UI state loss.
getSupportFragmentManager and getChildFragmentManager FragmentManager is class provided by the framework which is used to create transactions for adding, removing or replacing fragments. getSupportFragmentManager is associated with an activity. Consider it as a FragmentManager for your activity.
FragmentManager is the class responsible for performing actions on your app's fragments, such as adding, removing, or replacing them, and adding them to the back stack.
A little dive into sources
There is a boolean variable inside FragmentManager
class, called mStatedSaved
. This variable keeps track of the saved state depending on the activity's lifecycle callbacks. Here's the method, that throws well-known exception:
private void checkStateLoss() {
if (mStateSaved) {
throw new IllegalStateException(
"Can not perform this action after onSaveInstanceState");
}
...
}
This means, that as soon as this variable is changed to false
, then fragment transactions are free to be executed. This variable would become true
, when the state of the activity is being saved, i.e. onSaveInstanceState()
.
Back to the question
You said, that previously you had problems commiting a transaction from onActivityResult()
. That should mean, that previously mStateSaved
was not being assigned false
and currently it is. In deed it is so.
Here's onActivityResult()
implementation from O release:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mFragments.noteStateNotSaved();
...
}
Where, noteStateNotSaved()
would do following:
public void noteStateNotSaved() {
...
mStateSaved = false;
...
}
On the contrary, you can see the implementation of onActivityResult()
of Jelly Bean release:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
int index = requestCode>>16;
if (index != 0) {
index--;
if (mFragments.mActive == null || index = mFragments.mActive.size()) {
Log.w(TAG, "Activity result fragment index out of range: 0x"
+ Integer.toHexString(requestCode));
return;
}
Fragment frag = mFragments.mActive.get(index);
if (frag == null) {
Log.w(TAG, "Activity result no fragment exists for index: 0x"
+ Integer.toHexString(requestCode));
} else {
frag.onActivityResult(requestCode&0xffff, resultCode, data);
}
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
Nothing would change the value of mStateSaved
field, which would make an exception to be thrown if a transaction is committed.
In fact, the line mFragments.noteStateNotSaved()
was introduced in Kit-Kat release. As you can see by the commit's comment made by Dianne Hackborn:
ActivityFragment should clear the flag that state is saved when it receives
onNewIntent()
. This can happen before the activity is resumed, so we may not have cleared it yet. Also need to do the same thing foronActivityResult()
.
Sum up
Is
onActivityResult()
now guaranteed to be a safe place for fragment transactions?
Assuming you are using sources that include commit 4ccc001, which was made in October 2012 - yes, it is a safe place for fragment transactions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With