Commitallowingstateloss() in Fragment Activities

commitAllowingStateLoss() in fragment activities

If I understand correctly you mean : Is there any reason NOT to indiscriminately do this without re-evaluating each individual case where I use a fragment?

The answer is Yes - you should not do this without carefully re-evaluating each individual case where you use a fragment.

Of course, by preventing restarts due to config changes (screen rotations) you have eliminated one of the key problem areas : i.e. the user could rotate the screen AFTER a call to onSaveInstanceState but BEFORE the commitAllowingStateLoss. In this case a fragment or portion of UI might be lost. For an informal discussion of this, see this post.

But there are other situations you should consider before replacing commit by commitAllowingStateLoss.

  1. Basically, any UI updates between onSaveInstanceState and the commitAllowingStateLoss:
    Android: IllegalStateException - When is it thrown?

  2. If you have any headless fragments that update the UI of your activity then some of their updates might be lost (see this article).

  3. Android might "kill" a fragment because the phone/tab is running low on resources (see this answer).

Of course, if screen rotations are prevented, then onSaveInstanceState may not be called, in which case the window of opportunity for an update to be lost is increased.

If you do decide to use commitAllowingStateLoss then are things you can do to minimize the risks involved: e.g. consider doing a commit / executePendingTransactions when the parent activity is next restarted (I know you don't want to do this, but someone else might read this).

Finally (again in case someone else reads this - this is not relevant in your case) there are probably safer ways of handling an IllegalStateException than moving from commit to commitAllowStateLoss. e.g you could just stick with commit and handle the IllegalStateException. Alternatively, you may have hit a bug in Android and there might be a workaround.

commitAllowingStateLoss() and commit() fragment

I'd like to add informations to Aritra Roy (so far i readed, it's a really good answer).

I encountered the problem before, and i found that the main problem is that you are trying to make some async operations (HTTP, computations, ...) in another thread, wich is a good pratice, but you must inform your user AFTER receiving answers.

The main problem is that as it is async operations, there is no guarantee that the user is still on your activity/app anymore. And if he went away, there is no need to do UI changes. Moreover, as android may kill your app/activity for memory issues, you have no guarantees to be able to get your answer, and save it to be restored.
The problem is not only "the user can open another app" but "my activity can be recreated from configuration change" and you may be trying to do UI changes during activity recreation which would be really, really bad.

Using "commitAllowingStateLoss" is like saying "i don't care if the UI is not really in the good state". You can do it for little things (like activate a gif saying your download ended)... That's not a big issue, and this problem is not really worth dealing with it as "in general" the user will stay on your app.

But, the user did something, you're trying to get informations on the web, information is ready, and you have to show it when the user resume the app... the main word is "resume".

You must gather the data you needed into a variable (and if you can, a parcelable or primitive variable), and then, override your "onResume" or "onPostResume"(for activities) functions in the following way.

public void onResume/onPostResume() {
super.onResume/onPostResume();
if(someTreatmentIsPending) {
/*do what you need to do with your variable here : fragment
transactions, dialog showing...*/
}
}

Additional informations :
This topic and especially @jed answer, and @pjv, @Sufian comments to it.
This blog in order to understand why the bug occurs, and why proposed/accepted answers work.

Last word :
Just in case you wonder "why using a service is better than asyncTask". For what i understood, that's not really better. The main difference is that using service properly allow you to register/unregister handlers when your activity is paused/resumed. Therefore, you always get your answers when your activity is active, preventing the bug to occurs.

Notice that's not because the bug does not occurs that you are safe. If you made changes directly on your views, there is no fragmentTransactions involved, therefore, no guarantee that the change will be retained and recreated when the app is recreated, resumed, relaunched, or anything else.

commitAllowingStateLoss on DialogFragment

I think to prevent throwing IllegalStateException on DialogFragment might be better to use :

 YourDialogFragment dialogFragment = new YourDialogFragment();
fragmentManager.beginTransaction().add(dialogFragment, YourDialogFragment.TAG_FRAGMENT).commitAllowingStateLoss();

instead of using show() on DialogFragment.

popBackStack and commitAllowingStateLoss

After much research I have come to the conclusion that it is not possible to manipulate fragments on Android when the Activity is resumed. I have tried, as per the mentioned blog post, onPostResume() and onResumeFragments() to pop fragments from the backstack, and both result in intermittent crashes when released to production.

The downside to this reality is that if you wanted to, for example, display an end of level fragment, followed by an interstitial advertisement, followed by the next level (as a different fragment to the end of level fragment) then it is not possible to use fragments.

For my personal situation, I removed all fragments from my application. I keep using layouts, because editing the UI in XML is useful, but the Fragment lifecycle is unusable in its current state so I rolled my own "fragment" subsystem, but better because it can be manipulated from the Activities onResume.

I hope that one day Google will fix this because it makes developing for Android really unpleasant. Anyway, if anyone needs to use fragments, but doesn't like the typical onSaveInstanceState exception that you will invariably get, here is my "GameScreen" implementation (it's like a fragment, only better)

/**
* GameScreen
*/
public class GameScreen {

private int id;
private View view;
private ViewGroup viewGroup;
protected MainActivity mainActivity;

public GameScreen(MainActivity mainActivity, int id) {
this.mainActivity = mainActivity;
this.id = id;
}

public void create(LayoutInflater layoutInflater, ViewGroup viewGroup) {
this.viewGroup = viewGroup;
view = layoutInflater.inflate(id, viewGroup, false);
viewGroup.addView(view);
}

public void show() {
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View v = viewGroup.getChildAt(i);
if (v != view) {
v.setVisibility(View.INVISIBLE);
}
}

view.setVisibility(View.VISIBLE);
}
}

How do you handle fragment transactions when the state of the parent activity is bound to be saved?

You can commit fragment transactions again when your parent activity is resumed/started, even if it has been previous paused. The docs only mean that you cannot commit during the period of time where the activity is paused (and the state has been saved) and before it has been resumed again. If you return to your parent activity after visiting another activity, you are free to use fragment transactions again after Activity.onStart() has been called.

The reason for this restriction is that Android saves the state of fragments associated with an Activity during Activity.onSaveInstanceState(). If you try to make fragment transactions after this point, then you will be exposed to state loss if Android needs to recreate + restore that Activity (since the information it uses to recreate the Activity state was only the data that was captured in Activity.onSaveInstanceState()).

IllegalStateException: Can not perform this action after onSaveInstanceState with ViewPager

Please check my answer here. Basically I just had to :

@Override
protected void onSaveInstanceState(Bundle outState) {
//No call for super(). Bug on API Level > 11.
}

Don't make the call to super() on the saveInstanceState method. This was messing things up...

This is a known bug in the support package.

If you need to save the instance and add something to your outState Bundle you can use the following:

@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putString("WORKAROUND_FOR_BUG_19917_KEY", "WORKAROUND_FOR_BUG_19917_VALUE");
super.onSaveInstanceState(outState);
}

In the end the proper solution was (as seen in the comments) to use :

transaction.commitAllowingStateLoss();

when adding or performing the FragmentTransaction that was causing the Exception.



Related Topics



Leave a reply



Submit