Do rozwiązania problemu bezpiecznej zmiany stanu w trakcie Activity
cyklu życia, w szczególności w przypadku wyświetlania okien dialogowych, można zastosować następującą implementację : jeśli stan instancji został już zapisany (np. W wyniku zmiany konfiguracji), to odkłada je do czasu przywrócenia stanu zostało wykonane.
public abstract class XAppCompatActivity extends AppCompatActivity {
private String TAG = this.getClass().getSimpleName();
/** The retained fragment for this activity */
private ActivityRetainFragment retainFragment;
/** If true the instance state has been saved and we are going to die... */
private boolean instanceStateSaved;
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// get hold of retain Fragment we'll be using
retainFragment = ActivityRetainFragment.get(this, "Fragment-" + this.getClass().getName());
}
@Override
protected void onPostResume() {
super.onPostResume();
// reset instance saved state
instanceStateSaved = false;
// execute all the posted tasks
for (ActivityTask task : retainFragment.tasks) task.exec(this);
retainFragment.tasks.clear();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
instanceStateSaved = true;
}
/**
* Checks if the activity state has been already saved.
* After that event we are no longer allowed to commit fragment transactions.
* @return true if the instance state has been saved
*/
public boolean isInstanceStateSaved() {
return instanceStateSaved;
}
/**
* Posts a task to be executed when the activity state has not yet been saved
* @param task The task to be executed
* @return true if the task executed immediately, false if it has been queued
*/
public final boolean post(ActivityTask task)
{
// execute it immediately if we have not been saved
if (!isInstanceStateSaved()) {
task.exec(this);
return true;
}
// save it for better times
retainFragment.tasks.add(task);
return false;
}
/** Fragment used to retain activity data among re-instantiations */
public static class ActivityRetainFragment extends Fragment {
/**
* Returns the single instance of this fragment, creating it if necessary
* @param activity The Activity performing the request
* @param name The name to be given to the Fragment
* @return The Fragment
*/
public static ActivityRetainFragment get(XAppCompatActivity activity, String name) {
// find the retained fragment on activity restarts
FragmentManager fm = activity.getSupportFragmentManager();
ActivityRetainFragment fragment = (ActivityRetainFragment) fm.findFragmentByTag(name);
// create the fragment and data the first time
if (fragment == null) {
// add the fragment
fragment = new ActivityRetainFragment();
fm.beginTransaction().add(fragment, name).commit();
}
return fragment;
}
/** The queued tasks */
private LinkedList<ActivityTask> tasks = new LinkedList<>();
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// retain this fragment
setRetainInstance(true);
}
}
/** A task which needs to be performed by the activity when it is "fully operational" */
public interface ActivityTask {
/**
* Executed this task on the specified activity
* @param activity The activity
*/
void exec(XAppCompatActivity activity);
}
}
Następnie używając takiej klasy:
/** AppCompatDialogFragment implementing additional compatibility checks */
public abstract class XAppCompatDialogFragment extends AppCompatDialogFragment {
/**
* Shows this dialog as soon as possible
* @param activity The activity to which this dialog belongs to
* @param tag The dialog fragment tag
* @return true if the dialog has been shown immediately, false if the activity state has been saved
* and it is not possible to show it immediately
*/
public boolean showRequest(XAppCompatActivity activity, final String tag) {
return showRequest(activity, tag, null);
}
/**
* Shows this dialog as soon as possible
* @param activity The activity to which this dialog belongs to
* @param tag The dialog fragment tag
* @param args The dialog arguments
* @return true if the dialog has been shown immediately, false if the activity state has been saved
* and it is not possible to show it immediately
*/
public boolean showRequest(XAppCompatActivity activity, final String tag, final Bundle args)
{
return activity.post(new XAppCompatActivity.ActivityTask() {
@Override
public void exec(XAppCompatActivity activity) {
if (args!= null) setArguments(args);
show(activity.getSupportFragmentManager(), tag);
}
});
}
/**
* Dismiss this dialog as soon as possible
* @return true if the dialog has been dismissed immediately, false if the activity state has been saved
* and it is not possible to dismissed it immediately
*/
public boolean dismissRequest()
{
return dismissRequest(null);
}
/**
* Dismiss this dialog as soon as possible
* @param runnable Actions to be performed before dialog dismissal
* @return true if the dialog has been dismissed immediately, false if the activity state has been saved
* and it is not possible to dismissed it immediately
*/
public boolean dismissRequest(final Runnable runnable)
{
// workaround as in rare cases the activity could be null
XAppCompatActivity activity = (XAppCompatActivity)getActivity();
if (activity == null) return false;
// post the dialog dismissal
return activity.post(new XAppCompatActivity.ActivityTask() {
@Override
public void exec(XAppCompatActivity activity) {
if (runnable != null) runnable.run();
dismiss();
}
});
}
}
Możesz bezpiecznie wyświetlać okna dialogowe, nie martwiąc się o stan aplikacji:
public class TestDialog extends XAppCompatDialogFragment {
private final static String TEST_DIALOG = "TEST_DIALOG";
public static void show(XAppCompatActivity activity) {
new TestDialog().showRequest(activity, TEST_DIALOG);
}
public TestDialog() {}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
return new AlertDialog.Builder(getActivity(), R.style.DialogFragmentTheme /* or null as you prefer */)
.setTitle(R.string.title)
// set all the other parameters you need, e.g. Message, Icon, etc.
).create();
}
}
a następnie zadzwoń TestDialog.show(this)
ze swojego XAppCompatActivity
.
Jeśli chcesz utworzyć bardziej ogólną klasę okna dialogowego z parametrami, możesz zapisać je w a Bundle
z argumentami w show()
metodzie i pobrać je za pomocą getArguments()
in onCreateDialog()
.
Całe podejście może wydawać się nieco skomplikowane, ale po utworzeniu dwóch klas podstawowych dla działań i dialogów jest dość łatwe w użyciu i doskonale działa. Może być używany do innych Fragment
operacji bazowych, na które może mieć wpływ ten sam problem.