I'm well aware of what an IllegalStateException is and why it happens when you are trying to commit FragmentTransactions after instance state has been saved. I've read through most of the popular Stackoverflow questions on the subject as well as "The Blog Post."
I have a particular scenario in which I've created a Fragment to display a custom progress spinner while the app is waiting for some search results to come in. So my search Activity is running in singleTop mode and relies on onNewIntent()
to perform the search and display the spinner. This all works fine, but fails when run-time changes come in (like orientation changes). I'll get an IllegalStateException if I try removing the progress spinner Fragment after an orientation change, or when adding the spinner after a voice search. My setup looks like this:
@Override
protected void onNewIntent(Intent intent) {
if(intent.getAction().equals(Intent.ACTION_SEARCH)) {
performSearch(intent.getStringExtra(SearchManager.QUERY));
}
}
The performSearch()
method sends the request to my server and then adds the progress spinner Fragment like this...
private void showProgressSpinner() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setCustomAnimations(R.anim.fragment_fade_in, R.anim.fragment_fade_out);
ProgressFragment spinner = ProgressFragment.newInstance();
transaction.add(R.id.searchContainer, spinner, "spinner");
transaction.commit();
}
Then when the search results come in through an asynchronous callback, I remove the progress spinner Fragment like this...
private void dismissProgressSpinner() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setCustomAnimations(R.anim.fragment_fade_in, R.anim.fragment_fade_out);
ProgressFragment spinner = (ProgressFragment) fragmentManager.findFragmentByTag("spinner");
if(spinner != null) {
transaction.remove(spinner);
}
transaction.commit();
}
If an orientation change comes in while the progress spinner Fragment is being displayed, I get the IllegalStateException when the search results return and I try to remove the spinner Fragment. According to Alex Lockwood, putting the FragmentTransaction in onPostResume()
can help, since you are guaranteed to have the state restored by then. This indeed works, but I need to remove the Fragment in my asynchronous callback from the search results, not when the Activity resumes.
So my question is, how can I commit this Fragment transaction after a state change, but within my async callback? I've tried using commitAllowingStateLoss()
but I still get the exception because my spinner
Fragment is still referencing the old destroyed Activity. What's the best way to handle this?
I've tried using commitAllowingStateLoss() but I still get the exception because my spinner Fragment is still referencing the old destroyed Activity.
your fragment was recreated after config change, ie. after user have rotated screen - your spinner fragment will be destroyed and recreated, and after onAttach it will reference new activity instance. Also all of this process is done by android on UI thread in single message, so there is no chance that your async operation callback (which should execute also on UI thread) gets executed in the middle.
I assume here you are not creating some local reference to activity inside ProgressFragment.
You could write additional logic that would make sure your commit is called in valid moment. ie. in your activity onStart set some static boolean allowCommit to true, and in onPause set it to false. Also add some static variable, searchWasFinished, and in your async callback check if allowCommit is true if so then immediately remove spinner, if not then only set searchWasFinished to true. Inside your Activity.onStart check if searchWasFinished==true and if so then remove fragment with commit. This is just an idea, probably more logic would have to be put in it.