Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DialogFrag#show() from a Fragment throwing "IllegalStateException: Can not perform this action after onSaveInstanceState"

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:

  • 4.4.2 90%
  • 4.4.4 5%
  • 5.0 5%
like image 304
William Avatar asked Dec 06 '14 08:12

William


People also ask

What is DialogFragment used for?

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.

What is the difference between dialog and DialogFragment?

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.

How do I start a DialogFragment?

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 .

Is dialog fragment deprecated?

Stay organized with collections Save and categorize content based on your preferences. This class was deprecated in API level 28.


1 Answers

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

like image 196
William Avatar answered Sep 20 '22 08:09

William