Search code examples
androidandroid-fragmentsillegalstateexception

IllegalStateException when .replace fragment on restart


I am using FragmentTransaction.replace() to swap fragments in and out. The app starts up first time with no problem. An IllegalStateException is thrown when rotating the device because of a conflict between the savedInstanceState and commiting a new fragment transaction. No AsyncTask is involved.

One StackOverflow question suggests to put the setContentView() call in onResumeFragments(), but this seems to have no effect. Same with onPostResume().

Another StackOverflow question says to override onConfigurationChanged(). This works in that sense that it the exception doesn't occur because the Activity is not restarted. However, this prevents fragments that have different portrait and landscape layouts from switching between these layouts. Calling setContentView() in onConfigurationChanged() causes a similar error (IllegalArgumentException: Binary XML file line #25: Duplicate id 0x12345678, tag null, or parent id with another fragment)

Using fragmentTransaction.commitAllowingStateLoss() instead of .commit() causes IllegalStateException: Activity has been destroyed.

How do I get this to work?

More exception info:

java.lang.RuntimeException: Unable to start activity ComponentInfo{myapp/myap.MainActivity}:
android.view.InflateException: Binary XML file line #25: Error inflating class fragment at myapp.MainActivity.onResumeFragments(MainActivity.java:450)
Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState at > android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1533) at myapp.fragments.FragmentChange.onFragmentChange(FragmentChange.java:128) at myapp.MainActivity.onNavigationDrawerItemSelected(MainActivity.java:490) at myapp.fragments.NavigationDrawerFragment.selectItem(NavigationDrawerFragment.java:197) at myapp.fragments.NavigationDrawerFragment.onCreate(NavigationDrawerFragment.java:78) at myapp.MainActivity.onResumeFragments(MainActivity.java:450)

The sequence in the code upon rotating the device is:

MainActivity.onPause()  
MainActivity.saveInstanceState()  
NavigationDrawerFragment.onSaveInstanceState()  
MainActivity.onStop()
MainActivity.onDestroy()
MainActivity.onCreate()
    super.onCreate(savedInstanceState);
MainActivity.onResumeFragments()
    setContentView()
NavigationDrawerFragment.onCreate()
MainActivity.onNavigationDrawerItemSelected()
     fragmentTransaction.commit();

MainActivity:

public class MainActivity extends AppCompatActivity implements
        NavigationDrawerFragment.NavigationDrawerCallbacks {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
    }


    @Override
    public void onNavigationDrawerItemSelected(int position) {
        ...
        FragmentChangeEvent fragmentChangeEvent = new FragmentChangeEvent(null);
        FragmentChange fragmentChange = FragmentChange.getInstance( getSupportFragmentManager());
        fragmentChange.onFragmentChange(fragmentChangeEvent);
        ...
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        outState.putString("WORKAROUND_FOR_BUG_19917_KEY", "WORKAROUND_FOR_BUG_19917_VALUE");
        super.onSaveInstanceState(outState);
    }

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

        // causes onNavigationDrawerItemSelected() to be called, exception thrown 
        setContentView(myapp.R.layout.activity_main);

        mNavigationDrawerFragment = (NavigationDrawerFragment)
                getSupportFragmentManager().findFragmentById(myapp.R.id.navigation_drawer);

        mNavigationDrawerFragment.setUp( // Set up the drawer
                myapp.R.id.navigation_drawer,
                (DrawerLayout) findViewById(myapp.R.id.drawer_layout));
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);

        if current fragment is "Individual" { // pseudocode
            setContentView(R.layout.activity_main); // causes IllegalArgumentException
        }
    }
}

NavigationDrawerFragment

public class NavigationDrawerFragment extends Fragment {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
        mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false);

        if (savedInstanceState != null) {
            mCurrentSelectedPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION);
            mFromSavedInstanceState = true;
        }

        // Select either the default item (0) or the last selected item.
        selectItem(mCurrentSelectedPosition);
    }

    private void selectItem(int position) {

        mCurrentSelectedPosition = position;
        if (mDrawerListView != null) {
            mDrawerListView.setItemChecked(position, true);
        }
        if (mDrawerLayout != null) {
            mDrawerLayout.closeDrawer(mFragmentContainerView);
        }
        if (mCallbacks != null) {
            // calls MainActivity.onNavigationDrawerItemSelected() 
            mCallbacks.onNavigationDrawerItemSelected(position);
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt(STATE_SELECTED_POSITION, mCurrentSelectedPosition);
    }

}

FragmentChange

public class FragmentChange implements FragmentChangeListener {

      public static FragmentChange getInstance(FragmentManager fragmentManager) {
        if (instance == null) {
            instance = new FragmentChange(fragmentManager);
        }
        return instance;
    }

    // constructor
    private FragmentChange(FragmentManager fragmentManager) {
        mFragmentManager = fragmentManager;
    }

    @Override
    public void onFragmentChange(FragmentChangeEvent fragmentChangeEvent) {
        ...
        mPosition = fragmentChangeEvent.getPosition();

        FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
        Fragment fragment = EmployeesVerticalFragment.newInstance();
        fragmentTransaction.replace(myapp.R.id.container, fragment);

        fragmentTransaction.commit(); // IllegalState exception here
        ...
    }
}

A greatly reduced form of the project on github which reproduces the IllegalStateException:


Solution

  • The FragmentManager is an

    Interface for interacting with Fragment objects inside of an Activity.

    It strikes me as a particular bad idea to have save it in a static field and reuse and old FragmentManager for a new activity. This will necessarily lead to Activity has been destroyed, when the new activity interact with the manager from the old activity.

    In your code, replace

    FragmentChange.getInstance(getFragmentManager());
    

    by

    new FragmentChange(getFragmentManager());