
Fragment Transactions and Loss of Activity
For those who have not encountered this problem, I’ll explain with an example - at the end of a long background operation you display a dialog (yes Google does not recommend doing this, but the customer requires). If you minimize the application before showing the dialog by pressing the Home key, then an IllegalStateException will be thrown when the dialog is displayed. The same thing will happen if the wait dialog is shown and hides it at the end of background activity - calling the dismiss () method after saving the state will throw an exception.

The best article on this topic that I found googling a problem is Fragment Transactions & Activity State Loss. The article explains the problem, but gives only general advice, the problem itself remains unresolved. Perhaps some of the hawkers will be interested in translating the article, but for now I’ll briefly describe its meaning. Android system has the ability to complete any activity of your application and its fragments with a lack of memory. To hide this unfortunate fact from the user, Android saves the state of activity and restores it if necessary, so that the user does not even notice what disasters occurred at the code level. When you try to display a dialog after saving a state, in essence you are violating the saved state and such activity cannot be restored. Android solves this in the simplest way for itself - it throws an exception and does not allow committing fragments transaction. And your application just crashes.
There are a lot of ways to deal with this problem, they all boil down to postponing the transaction until after recovery, namely in the functions Activity.onPostResume or Fragment.onResume. The first thing that may come to mind is to put a flag in place of showing the dialog, check it in onResume and display the dialog there. In the case of onActivityResult, this will even work - because this function is always called before the activity state is restored. But in the case of background processing, you don’t even know what state the activity will be, but there is simply no simple method for checking the status of activity. Google's recommended way is to use Loader for background processing. However, this is not always convenient. For example, their Volley library does not use this template and there is no easy way to connect it.
I will not torment you with other unsuccessful attempts to get around the problem. I think many of you have some options, but I will share my decision.
Let's write a test application:
If you run it, then after three seconds it will display a dialog. But if before the expiration of three seconds you press the Home key, then when the dialog is displayed, the application will crash.
Now add the following class, it will track the status of activity for us:
In order not to repeat the routine actions of initializing and supporting the state processing class, we will render their basic activity:
All that remains to be done is to wrap the transaction using our new state handler:
In case the state allows, the code inside Runnable will be immediately executed. Otherwise, the code will be placed in the queue and will be executed after the activity is restored.
Similarly, you can use this class inside fragments, only instead of the onPostResume method, call the handler code from the onResume method of the base fragment.
The source code for the example can be found on github .

The best article on this topic that I found googling a problem is Fragment Transactions & Activity State Loss. The article explains the problem, but gives only general advice, the problem itself remains unresolved. Perhaps some of the hawkers will be interested in translating the article, but for now I’ll briefly describe its meaning. Android system has the ability to complete any activity of your application and its fragments with a lack of memory. To hide this unfortunate fact from the user, Android saves the state of activity and restores it if necessary, so that the user does not even notice what disasters occurred at the code level. When you try to display a dialog after saving a state, in essence you are violating the saved state and such activity cannot be restored. Android solves this in the simplest way for itself - it throws an exception and does not allow committing fragments transaction. And your application just crashes.
There are a lot of ways to deal with this problem, they all boil down to postponing the transaction until after recovery, namely in the functions Activity.onPostResume or Fragment.onResume. The first thing that may come to mind is to put a flag in place of showing the dialog, check it in onResume and display the dialog there. In the case of onActivityResult, this will even work - because this function is always called before the activity state is restored. But in the case of background processing, you don’t even know what state the activity will be, but there is simply no simple method for checking the status of activity. Google's recommended way is to use Loader for background processing. However, this is not always convenient. For example, their Volley library does not use this template and there is no easy way to connect it.
I will not torment you with other unsuccessful attempts to get around the problem. I think many of you have some options, but I will share my decision.
Let's write a test application:
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startAsyncTask();
}
private void startAsyncTask() {
new AsyncTask() {
@Override
protected Void doInBackground(Void... params) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
runOnUiThread(new Runnable() {
@Override
public void run() {
showMyDialog();
}
});
return null;
}
}.execute();
}
private void showMyDialog() {
new TestDialog().show(getSupportFragmentManager(), "dialog");
}
public class TestDialog extends DialogFragment {
public TestDialog(){}
@Override
public Dialog onCreateDialog (Bundle savedInstanceState){
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setMessage("Hello World");
builder.setPositiveButton("OK", null);
return builder.create();
}
}
}
If you run it, then after three seconds it will display a dialog. But if before the expiration of three seconds you press the Home key, then when the dialog is displayed, the application will crash.
Now add the following class, it will track the status of activity for us:
public class StateHandler {
/**
* Очередь объектов Runnable
*/
private final List queueBuffer = Collections.synchronizedList(new ArrayList());
/**
* Флаг, показывающий состояние активности
*/
private Activity activity;
/**
* Обработчик восстановления
*/
public final synchronized void resume(Activity activity) {
this.activity = activity;
while (queueBuffer.size() > 0) {
final Runnable runnable = queueBuffer.get(0);
queueBuffer.remove(0);
runnable.run();
}
}
/**
* Обработчик паузы
*/
public final synchronized void pause() {
activity = null;
}
/**
* Сохраняем Runnable если мы в состоянии паузы, либо сразу выполняем его код
*
* @param runnable объект Runnable, который будет запускаться.
*/
public final synchronized void run(Runnable runnable) {
if (activity == null) {
queueBuffer.add(runnable);
} else {
runnable.run();
}
}
}
In order not to repeat the routine actions of initializing and supporting the state processing class, we will render their basic activity:
public class BaseActivity extends ActionBarActivity {
protected StateHandler stateHandler = new StateHandler();
@Override
protected void onPause() {
super.onPause();
stateHandler.pause();
}
@Override
protected void onPostResume() {
super.onPostResume();
stateHandler.resume(this);
}
}
All that remains to be done is to wrap the transaction using our new state handler:
stateHandler.run(new Runnable() {
@Override
public void run() {
new TestDialog().show(getSupportFragmentManager(), "dialog");
}
});
In case the state allows, the code inside Runnable will be immediately executed. Otherwise, the code will be placed in the queue and will be executed after the activity is restored.
Similarly, you can use this class inside fragments, only instead of the onPostResume method, call the handler code from the onResume method of the base fragment.
The source code for the example can be found on github .