benkc
benkc

Reputation: 3382

onClick or onItemClick while paused results in "IllegalStateException: Can not perform this action after onSaveInstanceState"

I have a TabActivity which hosts 5x FragmentActivity. Several of these contain buttons or lists that, in their onClick() or onItemClick(), create and push a new fragment.

For the most part, this works fine, but if things are being a bit unresponsive, or a tester does something silly (press-and-hold a button or list item, use a different finger to switch tabs, then release the button/list -- 100% reproducible), I get the click event well after the activity has been paused and saved. See log snippet:

10-30 17:05:16.258  3415  3415 D BKC DEBUG: More.onSaveInstanceState()
10-30 17:05:16.258  3415  3415 D BKC DEBUG: MoreFragment.onPause()
10-30 17:05:17.309  3415  3415 D BKC DEBUG: MoreFragment.onItemClick()

After reading this article and various StackOverflow questions about fragment state loss, I don't see a good answer to how to fix this.

Is there a correct fix for this that I'm missing, or should I just go with my kludge of choice?

Upvotes: 13

Views: 1801

Answers (4)

Matt Quigley
Matt Quigley

Reputation: 7884

I can confirm that this is a problem in the support library and not your code. The best workaround is (probably) to use the answer given by @Doge which is to track the paused state yourself.

The reason it's a problem with the support library is that onClicks happen after a click's release. If you're using multiple fingers, you can release another click event somewhere else (such as another button) which changes the fragment. The support library gets its click event after the fragment changes, and does not check to confirm it is still in the foreground. That means you have to check it yourself.

Which means that any call to setCurrentTab() or setCurrentTabByTag() must include a check for the paused state if they want to avoid this crash. Please correct me if I'm wrong.

Upvotes: 2

Doge
Doge

Reputation: 6543

I think a more elegant solution here would be to simply ignore the click event. Set up a very simple boolean flag in your BaseActivity to keep track when your activity is paused:

class BaseActivity extends BaseFragmentActivity {
    private boolean isPaused;

    @Override
    protected void onPause() {
        isPaused = true;
        super.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        isPaused = false;
    }

    public boolean isPaused() {
        return isPaused;
    }
}

Whenever you get a click event, simply check if your activity is paused. If it is, then it is very safe to ignore the event, as it makes no sense to act on it.

@Override
public void onItemClick(AdapterView parent, View view, int position, long id) {
    if (isPaused()) {
        //But... we're paused. Ignore.
        return;
    }

    //Act upon legitimate click events here
}

Upvotes: 2

benkc
benkc

Reputation: 3382

After thinking about it some more, I believe that commitAllowingStateLoss() is in fact the right answer for this case. We know that we are in the onClick() or onItemClick() handler, so if state loss could occur, we know it's because of this hole that allows click events through after the onSaveInstanceState().

In my casual testing, the new fragment will in fact pop up when you go back to the relevant tab, since nothing was actually torn down. That's a bit surprising to the user, but probably acceptable for this edge case.

Upvotes: 4

Douglas Jones
Douglas Jones

Reputation: 2542

Since the Activity is paused and there really is no right action to take with the thing that was selected, perhaps the best action to take is to catch and eat the IllegalStateException. Let the UI do what it was in the process of doing and let the FragmentTransaction fall on the floor.

Upvotes: 1

Related Questions