Reputation: 3382
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.
commitAllowingStateLoss()
(unconditionally) is a workaround that could hide real bugs.OnClickListener
s and OnItemClickListener
s in onSaveInstanceState
would 100% prevent this, and it's kind of a PITA to do that for every button in every fragment.isAdded()
, but I can confirm that doesn't work.onSaveInstanceState()
and onRestoreInstanceState()
and check that in onClick(), but again, that's just a kludge. EDIT: Oh, fragment doesn't have onRestoreInstanceState()
, but I can twiddle the flag in onResume()
or whatever.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
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
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
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
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