Java
Android Development
IllegalStateException
onSaveInstanceState
Error Handling

java.lang.IllegalStateException Can not perform this action after onSaveInstanceState

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

The IllegalStateException: Can not perform this action after onSaveInstanceState is one of the most common crashes in Android development. It occurs when you try to commit a FragmentTransaction after the activity has saved its state (after onSaveInstanceState() has been called). At that point, the system has already captured a snapshot of the UI state, and committing a fragment transaction would create a state that the system cannot restore if the process is killed. The fix depends on the situation: use commitAllowingStateLoss(), check isStateSaved(), or restructure the code to avoid late commits.

When the Error Occurs

java
1// This crashes if called after onSaveInstanceState()
2public void showDialog() {
3    MyDialogFragment dialog = new MyDialogFragment();
4    dialog.show(getSupportFragmentManager(), "dialog");
5    // IllegalStateException: Can not perform this action after onSaveInstanceState
6}

The activity lifecycle leading to this crash:

 
1onResume()User presses HomeonPause()onSaveInstanceState()onStop()
23                                          State is saved here.
4                                  Fragment transactions after this point crash.

Common triggers include:

  • Async callbacks (network responses, timer callbacks) that arrive after the user navigated away
  • onActivityResult() in older Android versions (pre-API 26) being called before onResume()
  • Background service or broadcast receiver triggering a fragment transaction

Fix 1: commitAllowingStateLoss()

java
1// Replace commit() with commitAllowingStateLoss()
2public void showResultFragment(String data) {
3    ResultFragment fragment = ResultFragment.newInstance(data);
4    getSupportFragmentManager()
5        .beginTransaction()
6        .replace(R.id.container, fragment)
7        .commitAllowingStateLoss();
8}

commitAllowingStateLoss() suppresses the exception by accepting that the transaction might be lost if the process is killed and restored. Use this when the transaction is not critical to the user's saved state.

Fix 2: Check isStateSaved() Before Committing

java
1// Available in AndroidX Fragment 1.0+
2public void showDialog() {
3    if (!getSupportFragmentManager().isStateSaved()) {
4        MyDialogFragment dialog = new MyDialogFragment();
5        dialog.show(getSupportFragmentManager(), "dialog");
6    } else {
7        // Queue the action for later, or skip it
8        pendingDialogData = currentData;
9    }
10}
11
12@Override
13protected void onResumeFragments() {
14    super.onResumeFragments();
15    if (pendingDialogData != null) {
16        MyDialogFragment dialog = MyDialogFragment.newInstance(pendingDialogData);
17        dialog.show(getSupportFragmentManager(), "dialog");
18        pendingDialogData = null;
19    }
20}

isStateSaved() returns true after onSaveInstanceState() and false after onResume(). This lets you defer the transaction to when it is safe.

Fix 3: Move Logic to onPostResume() or onResumeFragments()

java
1private String pendingFragment = null;
2
3@Override
4protected void onActivityResult(int requestCode, int resultCode, Intent data) {
5    super.onActivityResult(requestCode, resultCode, data);
6    if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
7        // Don't commit here — state may not be restored yet
8        pendingFragment = data.getStringExtra("result");
9    }
10}
11
12@Override
13protected void onPostResume() {
14    super.onPostResume();
15    // Safe to commit fragment transactions here
16    if (pendingFragment != null) {
17        getSupportFragmentManager()
18            .beginTransaction()
19            .replace(R.id.container, ResultFragment.newInstance(pendingFragment))
20            .commit();
21        pendingFragment = null;
22    }
23}

onPostResume() is called after onResume() and after fragment state has been restored, making it safe for fragment transactions.

Fix 4: Use Lifecycle-Aware Components

java
1// Modern approach with ViewModel and LiveData
2public class MyViewModel extends ViewModel {
3    private final MutableLiveData<String> showDialogEvent = new MutableLiveData<>();
4
5    public LiveData<String> getShowDialogEvent() {
6        return showDialogEvent;
7    }
8
9    public void onNetworkResponse(String data) {
10        // Safe from any thread — LiveData delivers in onResume
11        showDialogEvent.postValue(data);
12    }
13}
14
15// In Activity or Fragment
16viewModel.getShowDialogEvent().observe(this, data -> {
17    if (data != null) {
18        MyDialogFragment.newInstance(data)
19            .show(getSupportFragmentManager(), "dialog");
20    }
21});

LiveData only delivers values when the observer (Activity/Fragment) is in an active lifecycle state (STARTED or RESUMED), avoiding the state-loss problem entirely.

Fix 5: DialogFragment.showAllowingStateLoss()

For DialogFragment, there is no built-in showAllowingStateLoss(), but you can create one:

java
1public static void showAllowingStateLoss(DialogFragment dialog,
2                                          FragmentManager manager,
3                                          String tag) {
4    FragmentTransaction ft = manager.beginTransaction();
5    ft.add(dialog, tag);
6    ft.commitAllowingStateLoss();
7}
8
9// Usage
10showAllowingStateLoss(new MyDialogFragment(), getSupportFragmentManager(), "dialog");

Or as an extension function in Kotlin:

kotlin
1fun DialogFragment.showAllowingStateLoss(manager: FragmentManager, tag: String) {
2    manager.beginTransaction()
3        .add(this, tag)
4        .commitAllowingStateLoss()
5}
6
7// Usage
8MyDialogFragment().showAllowingStateLoss(supportFragmentManager, "dialog")

Understanding the Activity Lifecycle

 
1onCreate()onStart()onResume()
2     ↓                        ↓
3[Activity is running]
4     ↓                        ↓
5onPause()
6     ↓                        ↓
7onSaveInstanceState()State saved here
8     ↓                        ↓
9onStop()
10     ↓                        ↓
11onDestroy()
12
13Fragment transactions are safe between onResume() and onSaveInstanceState().
14After onSaveInstanceState(), commit() throws IllegalStateException.

Common Pitfalls

  • Using commit() in async callbacks without lifecycle checks: Network responses, timers, and broadcast receivers can fire at any time. If the callback arrives after onSaveInstanceState(), commit() crashes. Always check isStateSaved() or use commitAllowingStateLoss() in async contexts.
  • Assuming onActivityResult() is safe for fragment transactions: On API levels before 26, onActivityResult() is called before onResume(), meaning the fragment state has not been restored yet. Defer fragment transactions to onPostResume() or onResumeFragments().
  • Overusing commitAllowingStateLoss(): While it prevents the crash, it means the fragment transaction may be silently lost if the system kills and restores the process. Only use it for non-critical UI updates (toasts, temporary dialogs) that the user does not need to see after returning.
  • Not handling configuration changes: Rotating the device calls onSaveInstanceState() followed by onDestroy() and onCreate(). Async callbacks that reference the old activity instance will crash. Use ViewModel and LiveData to survive configuration changes.
  • Calling dismiss() on a DialogFragment after state loss: dismiss() internally commits a fragment transaction and can also throw IllegalStateException. Use dismissAllowingStateLoss() in async contexts.

Summary

  • The crash occurs when committing fragment transactions after onSaveInstanceState() — the system cannot restore UI state that was modified after the snapshot
  • Use commitAllowingStateLoss() for non-critical transactions that can be safely lost
  • Check isStateSaved() to conditionally defer transactions to a safe lifecycle callback
  • Use onPostResume() or onResumeFragments() for transactions triggered by onActivityResult()
  • Prefer LiveData and ViewModel for modern apps — LiveData only delivers events in active lifecycle states
  • Never commit fragment transactions in async callbacks without lifecycle awareness

Course illustration
Course illustration

All Rights Reserved.