La seguente implementazione può essere utilizzata per risolvere il problema di eseguire in modo sicuro i cambiamenti di stato durante il Activity
ciclo di vita, in particolare per mostrare le finestre di dialogo: se lo stato dell'istanza è già stato salvato (ad esempio a causa di una modifica della configurazione), le rimanda fino a quando lo stato ripristinato stato eseguito.
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);
}
}
Quindi usando una classe come questa:
/** 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();
}
});
}
}
Puoi mostrare in sicurezza le finestre di dialogo senza preoccuparti dello stato dell'app:
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();
}
}
e poi chiama TestDialog.show(this)
dal tuo XAppCompatActivity
.
Se vuoi creare una classe di dialogo più generica con parametri, puoi salvarli in a Bundle
con gli argomenti nel show()
metodo e recuperarli con getArguments()
in onCreateDialog()
.
L'intero approccio potrebbe sembrare un po 'complesso, ma una volta create le due classi base per attività e dialoghi, è abbastanza facile da usare e funziona perfettamente. Può essere utilizzato per altre Fragment
operazioni basate che potrebbero essere interessate dallo stesso problema.