Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Checking if state is saved before committing a FragmentTransaction

I sometimes see the following stacktrace for a commit that can happen when the user isn't looking at the activity (after state's been saved):

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
    at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1327)
    at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1338)
    at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
    at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)

Looking at the Android source, this makes total sense:

private void checkStateLoss() {
        if (mStateSaved) {
            throw new IllegalStateException(
                    "Can not perform this action after onSaveInstanceState");
        }
        if (mNoTransactionsBecause != null) {
            throw new IllegalStateException(
                    "Can not perform this action inside of " + mNoTransactionsBecause);
        }
 }

Now, I wonder if there is any way (besides storing a class variable in on(Save/Restore)InstanceState) to check if a fragment is going to be committed in an undesirable state, this way I can store the transaction for later and make the commit at the appropriate time.

like image 375
hwrdprkns Avatar asked Feb 13 '13 18:02

hwrdprkns


People also ask

What is the process of starting a new fragment?

Step 1: Create a New Project To create a new project in Android Studio please refer to How to Create/Start a New Project in Android Studio. Step 2: Right-click on the First button inside java, then click on New. Step 2: Then Go to Fragment. Step 3: (The next step is to choose the Fragment type.

Can a fragment exist without activity?

Android app must have an Activity or FragmentActivity that handles the fragment. Fragment can't be initiated without Activity or FragmentActivity.


3 Answers

Starting from support library version 26.0.0 Beta 1 a new API is available in FragmentManager and Fragment classes:

FragmentManager and Fragment have an isStateSaved() method to allow querying whether or not a transaction will be allowed without state loss. This is especially useful to check when handling an onClick() event before executing any transaction.

From docs of android.support.v4.app.FragmentManager#isStateSaved():

Returns true if the FragmentManager's state has already been saved by its host. Any operations that would change saved state should not be performed if this method returns true. For example, any popBackStack() method, such as popBackStackImmediate() or any FragmentTransaction using commit() instead of commitAllowingStateLoss() will change the state and will result in an error.

This API will ship with framework's android.app.FragmentManager starting from Android O.

like image 72
azizbekian Avatar answered Oct 16 '22 12:10

azizbekian


Since you did not attach any example code, all i can guess is that you're using "wrong" method when committing transaction.

so, instead of using FragmentTransaction.commit(), you should use FragmentTransaction.commitAllowingStateLoss().

Also, there are reports and workarounds about this issue (or rather change in API behavior) in this google blog post.

like image 21
Tomo Avatar answered Oct 16 '22 12:10

Tomo


It is unluckily Android Fragment does not provide an API check if it is fine to commit transaction.

But we can add a boolean field in the attached activity helping us checking. Please ref the following code.

public class GlobalBaseActivity extends FragmentActivity {

    private boolean mAllowCommit;

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

        mAllowCommit = true;
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        mAllowCommit = false;
        super.onSaveInstanceState(outState);
    }

    @Override
    protected void onResumeFragments() {
        mAllowCommit = true;
        super.onResumeFragments();
    }

    public boolean allowFragmentCommit() {
        return mAllowCommit;
    } 

    public void callbackOnEvent() {
        if (allowFragmentCommit()){
            getFragmentManager().beginTransaction().add(new YourFragment(), TAG).commit();
        }
    }
}

As for why choosing onResumeFragment() as allowing transaction indicator, ref this good blog. It explains famous IllegalStateException in detail.

like image 13
peacepassion Avatar answered Oct 16 '22 12:10

peacepassion