Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

popBackStack causing java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState

I am getting this error below at the line of manager.popBackStack. Is there a way around this? It happens quite a it.

public void updateView(Fragment fragment) {

        IFragment currentFragment = (IFragment)fragment;

        FragmentManager manager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = manager.beginTransaction();
        fragmentTransaction.replace(R.id.content_frame, fragment);

        if(currentFragment != null)
        {
            if(currentFragment.isRoot())
            {
                manager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
            }
            else
            {
                fragmentTransaction.addToBackStack("test");
            }
        }

        fragmentTransaction.commitAllowingStateLoss();

        if(drawerManager.DrawerLayout != null) {
            drawerManager.DrawerLayout.closeDrawer(drawerManager.DrawerList);
        }
    }
 Fatal Exception: java.lang.IllegalStateException: Can not perform this
 action after onSaveInstanceState
        at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:2044)
        at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:2067)
        at android.support.v4.app.FragmentManagerImpl.popBackStack(FragmentManager.java:799)
        at com.exposure.activities.BaseActivity.updateView(BaseActivity.java:239)
        at com.exposure.activities.events.EventActivity.setupEvent(EventActivity.java:204)
        at com.exposure.activities.events.EventActivity.getData(EventActivity.java:117)
        at com.exposure.utilities.ActivityContainer.getData(ActivityContainer.java:83)
        at com.exposure.utilities.DataTask.onPostExecute(DataTask.java:37)
        at android.os.AsyncTask.finish(AsyncTask.java:695)
        at android.os.AsyncTask.-wrap1(Unknown Source)
        at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:712)
        at android.os.Handler.dispatchMessage(Handler.java:105)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6938)
       at java.lang.reflect.Method.invoke(Method.java)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)
like image 791
Mike Flynn Avatar asked Dec 01 '18 00:12

Mike Flynn


4 Answers

This is what worked for me is to check if fragment manger doesn't hava a saved state before popBackStack()

if (fragmentManager != null && !fragmentManager.isStateSaved()) {
    fragmentManager.popBackStack();
}
like image 112
Melad Avatar answered Nov 19 '22 19:11

Melad


As explained in Fragment Transactions & Activity State Loss under the bullet point "Avoid performing transactions inside asynchronous callback methods":

Avoid performing transactions inside asynchronous callback methods. This includes commonly used methods such as AsyncTask#onPostExecute() and LoaderManager.LoaderCallbacks#onLoadFinished(). The problem with performing transactions in these methods is that they have no knowledge of the current state of the Activity lifecycle when they are called.

This appears to be the issue that you are having since updateView() is called from an asynchronous task, but let's test that hypothesis.

The following demo app creates a fragment, simulates background processing and a callback that mimics your async callback. There is a flag in the code, mFixIt when set to true causes the app to behave properly (not blow up) and when false lets the app fail.

With mFixIt == false. The trigger is the home button that causes the app to go into a stopped state:

enter image description here

Here is the stack trace:

14967-15003 E/AndroidRuntime: FATAL EXCEPTION: Thread-2
    Process: com.example.illegalstatepopbackstack, PID: 14967
    java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
        at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:2080)
        at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:2106)
        at android.support.v4.app.FragmentManagerImpl.popBackStack(FragmentManager.java:832)
        at com.example.illegalstatepopbackstack.MainActivity.updateView(MainActivity.java:70)
        at com.example.illegalstatepopbackstack.ui.main.MainFragment$1.run(MainFragment.java:63)
        at java.lang.Thread.run(Thread.java:764)

Now with mFixIt == true. The difference this time is that the app recognizes an async callback while the activity is in a stopped state, records that this has happened and completes the processing when the app is restarted. The visual is simply pressing the home button and restoring from "recents". The app simply puts up the fragment at the start and when re-started changes the top TextView text and pops the fragment from the backstack.

enter image description here

As can be seen, processing completes as expected.

This is a trivial example. If your processing is more involved or you just want a more formal way of handling this situation I recommend taking a look at this solution.

Here is the code for the demo app:

MainActivity.java

public class MainActivity extends AppCompatActivity {
    // Set to true to fix the problem; false will cause the IllegalStateException
    private boolean mFixIt = false;

    private MainFragment mFragment;
    private TextView mTextView;
    private boolean mIsPaused;
    private boolean mUpdateViewNeeded;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_activity);
        mTextView = findViewById(R.id.textView);

        FragmentManager fm = getSupportFragmentManager();
        if (savedInstanceState == null) {
            // Create out fragment
            mFragment = MainFragment.newInstance();
            fm.beginTransaction()
                .replace(R.id.container, mFragment)
                .addToBackStack(FRAGMENT_TAG)
                .commit();
        } else {
            // Find the restored fragment.
            mFragment = (MainFragment) fm.findFragmentByTag(FRAGMENT_TAG);
        }
    }

    @Override
    protected void onStop() {
        super.onStop();

        // Simulate a background task that does something useful. This one just waits a few
        // second then does a callback to updateView(). The activity will be fully paused by then.
        mFragment.doSomethingInBackground();
        mIsPaused = true;
        Log.d("MainActivity","<<<< stopped");
    }

    @Override
    protected void onStart() {
        super.onStart();
        mIsPaused = false;
        if (mUpdateViewNeeded) {
            // Execute delayed processing now that the activity is resumed.
            updateView(getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG));
        }
    }

    public void updateView(Fragment fragment) {
        if (mIsPaused && mFixIt) {
            // Delay processing
            mUpdateViewNeeded = true;
        } else {
            // Do out update work. If we are paused, this will get an IllegalStateException. If
            // we are resumed, this will work as intended.
            mTextView.setText("Replaced...");
            getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
            mUpdateViewNeeded = false;
        }
    }

    public static final String FRAGMENT_TAG = "MyFragment";
}

MainFragment.java

public class MainFragment extends Fragment {

    MainActivity mMainActivity;

    public static MainFragment newInstance() {
        return new MainFragment();
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.main_fragment, container, false);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        mMainActivity = (MainActivity) context;
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mMainActivity = null;
    }

    @Override
    public void onStart() {
        super.onStart();

    }

    public void doSomethingInBackground() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (mMainActivity != null) {
                    mMainActivity.updateView(MainFragment.this);
                }
            }
        }).start();
    }
}

main_activity.xml

<TextView
    android:id="@+id/textView"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@android:color/holo_blue_light"
    android:gravity="center"
    android:text="To be replaced..."
    android:textSize="36sp"
    android:textStyle="bold" />

<FrameLayout
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" />

main_fragment.xml

<android.support.constraint.ConstraintLayout 
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/holo_green_light"
    tools:context=".ui.main.MainFragment">

    <TextView
        android:id="@+id/message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="MainFragment"
        android:textSize="36sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>
like image 38
Cheticamp Avatar answered Nov 19 '22 21:11

Cheticamp


Just call popBackStackImmediate() as the regular popBackStack() is asynchronous...

like image 28
Janeviemus Avatar answered Nov 19 '22 21:11

Janeviemus


the problem basically is:

IllegalStateException: Can not perform this action after onSaveInstanceState.

therefore, check with !isFinishing() && !isDestroyed() in .updateView()

because this would return true upon onSaveInstanceState() ...

alike this the situation can be mitigated and the crash prevented.

like image 1
Martin Zeitler Avatar answered Nov 19 '22 20:11

Martin Zeitler