On showing dialog i get “Can not perform this action after onSaveInstanceState”

前端 未结 17 1061
甜味超标
甜味超标 2020-11-29 18:37

Some users are reporting, if they use the quick action in the notification bar, they are getting a force close.

I show a quick action in the notification who calls t

相关标签:
17条回答
  • 2020-11-29 19:27

    please try to use FragmentTransaction instead of FragmentManager. I think the below code will solve your problem. If not, Please let me know.

    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    SnoozeDialog snoozeDialog = new SnoozeDialog();
    snoozeDialog.show(ft, "snooze_dialog");
    

    EDIT:

    Fragment Transaction

    Please check this link. I think it will solve you queries.

    0 讨论(0)
  • 2020-11-29 19:29

    If the dialog is not really important (it is okay to not-show it when the app closed/is no longer in view), use:

    boolean running = false;
    
    @Override
    public void onStart() {
        running = true;
        super.onStart();
    }
    
    @Override
    public void onStop() {
        running = false;
        super.onStop();
    }
    

    And open your dialog(fragment) only when we're running:

    if (running) {
        yourDialog.show(...);
    }
    

    EDIT, PROBABLY BETTER SOLUTION:

    Where onSaveInstanceState is called in the lifecycle is unpredictable, I think a better solution is to check on isSavedInstanceStateDone() like this:

    /**
     * True if SavedInstanceState was done, and activity was not restarted or resumed yet.
     */
    private boolean savedInstanceStateDone;
    
    @Override
    protected void onResume() {
        super.onResume();
    
        savedInstanceStateDone = false;
    }
    
    @Override
    protected void onStart() {
        super.onStart();
    
        savedInstanceStateDone = false;
    }
    
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        savedInstanceStateDone = true;
    }
    
    
    /**
     * Returns true if SavedInstanceState was done, and activity was not restarted or resumed yet.
     */
    public boolean isSavedInstanceStateDone() {
        return savedInstanceStateDone;
    }
    
    0 讨论(0)
  • 2020-11-29 19:29

    use this code

    FragmentTransaction ft = fm.beginTransaction();
            ft.add(yourFragment, "fragment_tag");
            ft.commitAllowingStateLoss();
    

    instead of

    yourFragment.show(fm, "fragment_tag");
    
    0 讨论(0)
  • 2020-11-29 19:29

    The following implementation can be used to solve the problem of performing safely state changes during the Activity lifecycle, in particular for showing dialogs: if the instance state has already been saved (e.g. due to a configuration change), it postpones them until the resumed state has been performed.

    public abstract class XAppCompatActivity extends AppCompatActivity {
    
        private String TAG = this.getClass().getSimpleName();
    
        /** The retained fragment for this activity */
        private ActivityRetainFragment retainFragment;
    
        /** If true the instance state has been saved and we are going to die... */
        private boolean instanceStateSaved;
    
        @Override
        protected void onPostCreate(Bundle savedInstanceState) {
            super.onPostCreate(savedInstanceState);
    
            // get hold of retain Fragment we'll be using
            retainFragment = ActivityRetainFragment.get(this, "Fragment-" + this.getClass().getName());
        }
    
        @Override
        protected void onPostResume() {
            super.onPostResume();
    
            // reset instance saved state
            instanceStateSaved = false;
    
            // execute all the posted tasks
            for (ActivityTask task : retainFragment.tasks) task.exec(this);
            retainFragment.tasks.clear();
        }
    
        @Override
        protected void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            instanceStateSaved = true;
        }
    
        /**
         * Checks if the activity state has been already saved.
         * After that event we are no longer allowed to commit fragment transactions.
         * @return true if the instance state has been saved
         */
        public boolean isInstanceStateSaved() {
            return instanceStateSaved;
        }
    
        /**
         * Posts a task to be executed when the activity state has not yet been saved
         * @param task The task to be executed
         * @return true if the task executed immediately, false if it has been queued
         */
        public final boolean post(ActivityTask task)
        {
            // execute it immediately if we have not been saved
            if (!isInstanceStateSaved()) {
                task.exec(this);
                return true;
            }
    
            // save it for better times
            retainFragment.tasks.add(task);
            return false;
        }
    
        /** Fragment used to retain activity data among re-instantiations */
        public static class ActivityRetainFragment extends Fragment {
    
            /**
             * Returns the single instance of this fragment, creating it if necessary
             * @param activity The Activity performing the request
             * @param name The name to be given to the Fragment
             * @return The Fragment
             */
            public static ActivityRetainFragment get(XAppCompatActivity activity, String name) {
    
                // find the retained fragment on activity restarts
                FragmentManager fm = activity.getSupportFragmentManager();
                ActivityRetainFragment fragment = (ActivityRetainFragment) fm.findFragmentByTag(name);
    
                // create the fragment and data the first time
                if (fragment == null) {
                    // add the fragment
                    fragment = new ActivityRetainFragment();
                    fm.beginTransaction().add(fragment, name).commit();
                }
    
                return fragment;
            }
    
            /** The queued tasks */
            private LinkedList<ActivityTask> tasks = new LinkedList<>();
    
            @Override
            public void onCreate(Bundle savedInstanceState)
            {
                super.onCreate(savedInstanceState);
    
                // retain this fragment
                setRetainInstance(true);
            }
    
        }
    
        /** A task which needs to be performed by the activity when it is "fully operational" */
        public interface ActivityTask {
    
            /**
             * Executed this task on the specified activity
             * @param activity The activity
             */
            void exec(XAppCompatActivity activity);
        }
    }
    

    Then using a class like this:

    /** AppCompatDialogFragment implementing additional compatibility checks */
    public abstract class XAppCompatDialogFragment extends AppCompatDialogFragment {
    
        /**
         * Shows this dialog as soon as possible
         * @param activity The activity to which this dialog belongs to
         * @param tag The dialog fragment tag
         * @return true if the dialog has been shown immediately, false if the activity state has been saved
         *         and it is not possible to show it immediately
         */
        public boolean showRequest(XAppCompatActivity activity, final String tag) {
            return showRequest(activity, tag, null);
        }
    
        /**
         * Shows this dialog as soon as possible
         * @param activity The activity to which this dialog belongs to
         * @param tag The dialog fragment tag
         * @param args The dialog arguments
         * @return true if the dialog has been shown immediately, false if the activity state has been saved
         *         and it is not possible to show it immediately
         */
        public boolean showRequest(XAppCompatActivity activity, final String tag, final Bundle args)
        {
            return activity.post(new XAppCompatActivity.ActivityTask() {
                @Override
                public void exec(XAppCompatActivity activity) {
                    if (args!= null) setArguments(args);
                    show(activity.getSupportFragmentManager(), tag);
                }
            });
        }
    
        /**
         * Dismiss this dialog as soon as possible
         * @return true if the dialog has been dismissed immediately, false if the activity state has been saved
         *         and it is not possible to dismissed it immediately
         */
        public boolean dismissRequest()
        {
            return dismissRequest(null);
        }
    
        /**
         * Dismiss this dialog as soon as possible
         * @param runnable Actions to be performed before dialog dismissal
         * @return true if the dialog has been dismissed immediately, false if the activity state has been saved
         *         and it is not possible to dismissed it immediately
         */
        public boolean dismissRequest(final Runnable runnable)
        {
            // workaround as in rare cases the activity could be null
            XAppCompatActivity activity = (XAppCompatActivity)getActivity();
            if (activity == null) return false;
    
            // post the dialog dismissal
            return activity.post(new XAppCompatActivity.ActivityTask() {
                @Override
                public void exec(XAppCompatActivity activity) {
                    if (runnable != null) runnable.run();
                    dismiss();
                }
            });
        }
    }
    

    You can safely show dialogs without worrying about the app state:

    public class TestDialog extends XAppCompatDialogFragment {
    
        private final static String TEST_DIALOG = "TEST_DIALOG";
    
        public static void show(XAppCompatActivity activity) {
            new TestDialog().showRequest(activity, TEST_DIALOG);
        }
    
        public TestDialog() {}
    
        @NonNull
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState)
        {
            return new AlertDialog.Builder(getActivity(), R.style.DialogFragmentTheme /* or null as you prefer */)
                    .setTitle(R.string.title)
                    // set all the other parameters you need, e.g. Message, Icon, etc.
                    ).create();
        }
    }
    

    and then call TestDialog.show(this) from within your XAppCompatActivity.

    If you want to create a more generic dialog class with parameters, you can save them in a Bundle with the arguments in the show() method and retrieve them with getArguments() in onCreateDialog().

    The whole approach could seem a bit complex, but once you have created the two base classes for activities and dialogs, it is quite easy to use and is perfectly working. It can be used for other Fragment based operations which could be affected by the same problem.

    0 讨论(0)
  • 2020-11-29 19:31

    Make your dialog fragment object global and call dismissAllowingStateLoss() in onPause() method

    @Override
    protected void onPause() {
        super.onPause();
    
        if (dialogFragment != null) {
            dialogFragment.dismissAllowingStateLoss();
        }
    }
    

    Don't forget to assign value in fragment and call show() on button click or where ever.

    0 讨论(0)
提交回复
热议问题