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

前端 未结 17 1059
甜味超标
甜味超标 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:11
    1. Add this class to your project: (must be in android.support.v4.app package)
    package android.support.v4.app;
    
    
    /**
     * Created by Gil on 8/16/2017.
     */
    
    public class StatelessDialogFragment extends DialogFragment {
        /**
         * Display the dialog, adding the fragment using an existing transaction and then committing the
         * transaction whilst allowing state loss.
    * * I would recommend you use {@link #show(FragmentTransaction, String)} most of the time but * this is for dialogs you reallly don't care about. (Debug/Tracking/Adverts etc.) * * @param transaction * An existing transaction in which to add the fragment. * @param tag * The tag for this fragment, as per * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}. * @return Returns the identifier of the committed transaction, as per * {@link FragmentTransaction#commit() FragmentTransaction.commit()}. * @see StatelessDialogFragment#showAllowingStateLoss(FragmentManager, String) */ public int showAllowingStateLoss(FragmentTransaction transaction, String tag) { mDismissed = false; mShownByMe = true; transaction.add(this, tag); mViewDestroyed = false; mBackStackId = transaction.commitAllowingStateLoss(); return mBackStackId; } /** * Display the dialog, adding the fragment to the given FragmentManager. This is a convenience * for explicitly creating a transaction, adding the fragment to it with the given tag, and * committing it without careing about state. This does not add the transaction to the * back stack. When the fragment is dismissed, a new transaction will be executed to remove it * from the activity.
    * * I would recommend you use {@link #show(FragmentManager, String)} most of the time but this is * for dialogs you reallly don't care about. (Debug/Tracking/Adverts etc.) * * * @param manager * The FragmentManager this fragment will be added to. * @param tag * The tag for this fragment, as per * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}. * @see StatelessDialogFragment#showAllowingStateLoss(FragmentTransaction, String) */ public void showAllowingStateLoss(FragmentManager manager, String tag) { mDismissed = false; mShownByMe = true; FragmentTransaction ft = manager.beginTransaction(); ft.add(this, tag); ft.commitAllowingStateLoss(); } }
    1. Extend StatelessDialogFragment instead of DialogFragment
    2. Use the method showAllowingStateLoss instead of show

    3. Enjoy ;)

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

    If you override show() function, don't do this:

    override fun show(manager: FragmentManager, tag: String?) {
        // mDismissed = false; is removed -> lead to wrong state
        // mShownByMe = true; is removed -> lead to wrong state
        val ft = manager.beginTransaction()
        ft.add(this, tag)
        ft.commitAllowingStateLoss()
    }
    

    It maybe lead to wrong state of dialog

    Just do:

    override fun show(manager: FragmentManager, tag: String?) {
        try {
            super.show(manager, tag)
        } catch (e: Exception) {
            val ft = manager.beginTransaction()
            ft.add(this, tag)
            ft.commitAllowingStateLoss()
        }
    }
    
    0 讨论(0)
  • 2020-11-29 19:14
    private void showSnoozeDialog() {
        FragmentManager fm = getSupportFragmentManager();
        SnoozeDialog snoozeDialog = new SnoozeDialog();
        // snoozeDialog.show(fm, "snooze_dialog");
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.add(snoozeDialog, "snooze_dialog");
        ft.commitAllowingStateLoss();
    }
    

    ref: link

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

    That mean you commit() (show() in case of DialogFragment) fragment after onSaveInstanceState().

    Android will save your fragment state at onSaveInstanceState(). So, if you commit() fragment after onSaveInstanceState() fragment state will be lost.

    As a result, if Activity get killed and recreate later the fragment will not add to activity which is bad user experience. That's why Android does not allow state loss at all costs.

    The easy solution is to check whether state already saved.

    boolean mIsStateAlreadySaved = false;
    boolean mPendingShowDialog = false;
    
    @Override
    public void onResumeFragments(){
        super.onResumeFragments();
        mIsStateAlreadySaved = false;
        if(mPendingShowDialog){
            mPendingShowDialog = false;
            showSnoozeDialog();
        }
    }
    
    @Override
    public void onPause() {
        super.onPause();
        mIsStateAlreadySaved = true;
    }
    
    private void showSnoozeDialog() {
        if(mIsStateAlreadySaved){
            mPendingShowDialog = true;
        }else{
            FragmentManager fm = getSupportFragmentManager();
            SnoozeDialog snoozeDialog = new SnoozeDialog();
            snoozeDialog.show(fm, "snooze_dialog");
        }
    }
    

    Note: onResumeFragments() will call when fragments resumed.

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

    This error appears to be occurring because input events (such as key down or onclick events) are getting delivered after onSaveInstanceState is called.

    The solution is to override onSaveInstanceState in your Activity and cancel any pending events.

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            final View rootView = findViewById(android.R.id.content);
            if (rootView != null) {
                rootView.cancelPendingInputEvents();
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-29 19:18

    I have run in to this problem for years.
    The Internets are littered with scores (hundreds? thousands?) of discussions about this, and confusion and disinformation in them seems aplenty.
    To make the situation worse, and in the spirit of the xkcd "14 standards" comic, I am throwing in my answer in to the ring.

    The cancelPendingInputEvents(), commitAllowingStateLoss(), catch (IllegalStateException e), and similar solutions all seem atrocious.

    Hopefully the following easily shows how to reproduce and fix the problem:

    private static final Handler sHandler = new Handler();
    private boolean mIsAfterOnSaveInstanceState = true;
    
    @Override
    protected void onSaveInstanceState(Bundle outState)
    {
        super.onSaveInstanceState(outState);
        mIsAfterOnSaveInstanceState = true; // <- To repro, comment out this line
    }
    
    @Override
    protected void onPostResume()
    {
        super.onPostResume();
        mIsAfterOnSaveInstanceState = false;
    }
    
    @Override
    protected void onResume()
    {
        super.onResume();
        sHandler.removeCallbacks(test);
    }
    
    @Override
    protected void onPause()
    {
        super.onPause();
        sHandler.postDelayed(test, 5000);
    }
    
    Runnable test = new Runnable()
    {
        @Override
        public void run()
        {
            if (mIsAfterOnSaveInstanceState)
            {
                // TODO: Consider saving state so that during or after onPostResume a dialog can be shown with the latest text
                return;
            }
    
            FragmentManager fm = getSupportFragmentManager();
            DialogFragment dialogFragment = (DialogFragment) fm.findFragmentByTag("foo");
            if (dialogFragment != null)
            {
                dialogFragment.dismiss();
            }
    
            dialogFragment = GenericPromptSingleButtonDialogFragment.newInstance("title", "message", "button");
            dialogFragment.show(fm, "foo");
    
            sHandler.postDelayed(test, 5000);
        }
    };
    
    0 讨论(0)
提交回复
热议问题