سؤال

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.

هل كانت مفيدة؟

المحلول 2

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.

نصائح أخرى

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.

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.

RuntimeExceptions (and IllegalStateException is one of them) most often mean your code is incorrect about how it tried to achieve something. Trying to handle such exception (for example by catching it) is also wrong. Your code should never behave the way Android would throw exception like this on you.

If you use Handlers and post or send message to be handled in the future, you need to clear the queue before going out of resumed state. Also you cannot just start AsyncTask from Activity and commit transaction in onPostExecute because user could go back from your Activity. You need to cancel it and check if it was cancelled.

There are many examples like these and all are caused by "temporary" memory leaks, like I like to call them.

Basically your code is bad and without providing it, it is impossible to tell how.

ft.commitAllowingStateLose() is your best bet in this situation. However, as stated, there is no guarantee that your state is going to persist.

You should call method commit only in these methods OnCreate, OnResumeFragments,OnPostResume. Details you can read here Fragment Transactions & Activity State Loss

Here's a Kotlin example leveraging the latest isStateSaved() FragmentManager method: https://proandroiddev.com/kotlin-extensions-to-commit-fragments-safely-de06218a1f4

See i also had this issue , i could not find a solution any where. I tried a work around .It works like this. Make the fragment transaction from a Handler instead. The handler should be invoked with a 2000 ms DELAY . Eg. on calling makeTransaction

void makeTransaction()
{
  handler.sendEmptyMessageDelayed(0,2000);
}

void actuallyMakeTransaction()
{
 // transaction code
}

Handler handler = new Handler()
{
void handleMessage(Message msg)
{
   actuallyMakeTransaction();
}
}

It solved the problem for me

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top