Just to be clear, I have read the dozen top SO questions on "IllegalStateException: Can not perform this action after onSaveInstanceState" and I have read Alex Lockwood's blog post on the issue http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html
So I'm not asking this blindly.
I have a very simple use case case that doesn't involve AsyncTask or any background processing.
I have a Fragment that contains a button. On the onClickListener for the button, I create a DialogFragment and show it.
public final class OverviewFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.overview_fragment, container, false);
startNewGameButton = (Button) view.findViewById(R.id.buttonNewGame);
startNewGameButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
final NewGameFragment dialogFrag = NewGameFragment.create(getApplication());
dialogFrag.show(getFragmentManager(), NewGameFragment.FRAGMENT_TAG);
}
});
}
[NewGameFragment]
public final class NewGameFragment extends DialogFragment {
public static final String FRAGMENT_TAG = "NewGameFragment";
private static final String MESSAGE = "message";
public static NewGameFragment create(Context context) {
final AppsPreferences prefs = new AppPreferences(context);
final int startOption = prefs.getGameStartOption();
final Bundle bundle = new Bundle();
bundle.putString(MESSAGE, getMessage(context, startOption));
final NewGameFragment fragment = new NewGameFragment();
fragment.setArguments(bundle);
return fragment;
}
@Override
public final Dialog onCreateDialog(Bundle savedInstanceState) {
final String message = getArguments().getString(MESSAGE);
final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
.setTitle(R.string.progress_startGame_title)
.setMessage(message);
builder.setPositiveButton(R.string.progress_startGame_raceButton, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
new RaceAction().execute();
}
});
builder.setNegativeButton(R.string.progress_startGame_eventButton, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
new EventAction().execute();
}
});
final Dialog dialog = builder.create();
dialog.setCanceledOnTouchOutside(false); // Whether clicking outside the dialog closes the dialog.
return dialog;
}
}
[Stacktrace]
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.void checkStateLoss()(SourceFile:1365)
at android.support.v4.app.FragmentManagerImpl.void enqueueAction(java.lang.Runnable,boolean)(SourceFile:1383)
at android.support.v4.app.BackStackRecord.int commitInternal(boolean)(SourceFile:636)
at android.support.v4.app.BackStackRecord.int commit()(SourceFile:615)
at android.support.v4.app.DialogFragment.void show(android.support.v4.app.FragmentManager,java.lang.String)(SourceFile:138)
at au.com.xandar.thegame.overview.OverviewFragment$1.void onClick(android.view.View)(SourceFile:160)
at android.view.View.performClick(View.java:4162)
at android.view.View$PerformClick.run(View.java:17082)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4867)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1007)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:774)
at dalvik.system.NativeStart.main(Native Method)
NB the Fragment and DialogFragment both come from support-v4:21.0.0
I am seeing this on a range of devices running 4.4. But at least one instance has occurred on a Nexus 7 running 5.0.
I have not been able to replicate this myself. Not even by introducing an artificial delay into the onClick and attempting to rotate, back, home the app.
So since the FragmentTransaction
(for DialogFrag#show()
) is being created and committed on the UI Thread directly from onClick()
, how can the Fragment
already have proceeded past onSaveInstanceState()
?
Does it mean that I need to check the state of the Activity Lifecycle at the start of every user input? - very bad (the Lifecycle is meant to be handling that for me. I shouldn't be receiving user input if the Activity is already past onPause()
)
Does it mean I need to check the state of the Activity Lifecycle prior to every statement during execution of user input? - broken bad !!
What can I do to stop this occurring?
Further info:
After running in the wild for several days I can categorically say that getChildFragmentManager()
is not the solution.
Failure occurs for the following Android versions:
DialogFragment is a utility class which extends the Fragment class. It is a part of the v4 support library and is used to display an overlay modal window within an activity that floats on top of the rest of the content. Essentially a DialogFragment displays a Dialog but inside a Fragment.
Dialog: A dialog is a small window that prompts the user to make a decision or enter additional information. DialogFragment: A DialogFragment is a special fragment subclass that is designed for creating and hosting dialogs.
To create a DialogFragment , first create a class that extends DialogFragment , and override onCreateDialog() , as shown in the following example. Similar to how onCreateView() should create a root View in an ordinary fragment, onCreateDialog() should create a Dialog to display as part of the DialogFragment .
Stay organized with collections Save and categorize content based on your preferences. This class was deprecated in API level 28.
OK, as far as I can tell this is a bug in the AOSP as I have also seen an instance of this from pure Android stack (ie nothing of my code at all).
So it looks like there is a threading issue in the Activity/Fragment lifecycle in which a UI thread can get priority to respond to a button click AFTER the Activity/Fragment has already saved it's state.
My work around which has been 100% successful so far is to catch the IllegalStateException and schedule the dialog show for the next time the Activity/Fragment becomes active using a PauseHandler https://stackoverflow.com/a/25322330/493682
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