Al mostrar el cuadro de diálogo, aparece "No se puede realizar esta acción después de onSaveInstanceState"

121

Algunos usuarios están informando, si usan la acción rápida en la barra de notificaciones, están obteniendo un cierre forzado.

Muestro una acción rápida en la notificación que llama a la clase "TestDialog" . En la clase TestDialog después de presionar el botón "snooze", mostraré el SnoozeDialog.

private View.OnClickListener btnSnoozeOnClick() {
    return new View.OnClickListener() {

        public void onClick(View v) {
            showSnoozeDialog();
        }
    };
}

private void showSnoozeDialog() {
    FragmentManager fm = getSupportFragmentManager();
    SnoozeDialog snoozeDialog = new SnoozeDialog();
    snoozeDialog.show(fm, "snooze_dialog");
}

El error es *IllegalStateException: Can not perform this action after onSaveInstanceState*.

La línea de código donde se dispara la IllegarStateException es:

snoozeDialog.show(fm, "snooze_dialog");

La clase está ampliando "FragmentActivity" y la clase "SnoozeDialog" está ampliando "DialogFragment".

Aquí está el seguimiento de pila completo del error:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1327)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1338)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
at android.support.v4.app.DialogFragment.show(DialogFragment.java:127)
at com.test.testing.TestDialog.f(TestDialog.java:538)
at com.test.testing.TestDialog.e(TestDialog.java:524)
at com.test.testing.TestDialog.d(TestDialog.java:519)
at com.test.testing.g.onClick(TestDialog.java:648)
at android.view.View.performClick(View.java:3620)
at android.view.View$PerformClick.run(View.java:14292)
at android.os.Handler.handleCallback(Handler.java:605)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4507)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:790)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:557)
at dalvik.system.NativeStart.main(Native Method)

No puedo reproducir este error, pero recibo muchos informes de errores.

¿Alguien puede ayudarme, cómo puedo solucionar este error?

chrisonline
fuente
2
¿Encontraste una solución? Tengo el mismo problema que tú. He preguntado aquí: stackoverflow.com/questions/15730878/… Por favor, revise mi pregunta y vea la posible solución que no funciona para mi caso. Tal vez funcione para ti.
rootpanthera
Aún no hay solución :-( Y tu sugerencia ya se agregó a mi clase.
chrisonline
Verifique la respuesta aceptada desde aquí. Esto resolvió mi problema: stackoverflow.com/questions/14177781/…
bogdan
4
¿Tu actividad está visible cuando se activa este cuadro de diálogo? Parece que esto puede deberse a que su aplicación intenta mostrar un cuadro de diálogo adjunto a una actividad que se pausó / detuvo.
Kai
stackoverflow.com/questions/7575921/… has probado esto, estoy seguro.
Orión

Respuestas:

66

Este es un problema común . Resolvimos este problema anulando show () y manejando la excepción en la clase extendida DialogFragment

public class CustomDialogFragment extends DialogFragment {

    @Override
    public void show(FragmentManager manager, String tag) {
        try {
            FragmentTransaction ft = manager.beginTransaction();
            ft.add(this, tag);
            ft.commit();
        } catch (IllegalStateException e) {
            Log.d("ABSDIALOGFRAG", "Exception", e);
        }
    }
}

Tenga en cuenta que la aplicación de este método no alterará los campos internos de DialogFragment.class:

boolean mDismissed;
boolean mShownByMe;

Esto puede dar lugar a resultados inesperados en algunos casos. Mejor use commitAllowingStateLoss () en lugar de commit ()

Rafael
fuente
3
Pero, ¿por qué ocurre este problema? ¿Está bien ignorar el error? ¿Qué pasa cuando lo haces? Después de todo, al hacer clic, significa que la actividad está activa y en buen estado ... De todos modos, he informado sobre esto aquí porque considero que es un error: code.google.com/p/android/issues/detail?id= 207269
desarrollador de Android
1
Entonces, ¿podrías destacar y / o comentar allí?
desarrollador de Android
2
es mejor llamar a super.show (administrador, etiqueta) dentro de la cláusula try-catch. Las banderas propiedad de DialogFragment pueden mantenerse seguras de esta manera
Shayan_Aryan
20
En este punto, puede llamar a commitAllowingStateLoss () en lugar de commit (). No se plantearía la excepción.
ARLabs
1
@ARLabs Me imagino que en este caso también sería mejor para el respondedor, ya que si simplemente detecta la excepción como se muestra aquí, el cuadro de diálogo definitivamente no se mostrará. Es mejor mostrar el cuadro de diálogo si puede y es posible que desaparezca si es necesario restaurar el estado. También mantenga el uso de memoria de la aplicación bajo en segundo plano para que no sea probable que se destruya.
androidguy
27

Eso significa que usted commit()( show()en el caso de DialogFragment) fragmenta después onSaveInstanceState().

Android guardará el estado de su fragmento en onSaveInstanceState(). Por lo tanto, si commit()fragmenta tras onSaveInstanceState()fragmento, se perderá el estado.

Como resultado, si la actividad se elimina y se vuelve a crear más tarde, el fragmento no se agregará a la actividad, lo cual es una mala experiencia para el usuario. Por eso Android no permite la pérdida de estado a toda costa.

La solución fácil es comprobar si el estado ya se ha guardado.

boolean mIsStateAlreadySaved = false;
boolean mPendingShowDialog = false;

@Override
public void onResumeFragments(){
    super.onResumeFragments();
    mIsStateAlreadySaved = false;
    if(mPendingShowDialog){
        mPendingShowDialog = false;
        showSnoozeDialog();
    }
}

@Override
public void onPause() {
    super.onPause();
    mIsStateAlreadySaved = true;
}

private void showSnoozeDialog() {
    if(mIsStateAlreadySaved){
        mPendingShowDialog = true;
    }else{
        FragmentManager fm = getSupportFragmentManager();
        SnoozeDialog snoozeDialog = new SnoozeDialog();
        snoozeDialog.show(fm, "snooze_dialog");
    }
}

Nota: onResumeFragments () llamará cuando se reanuden los fragmentos.

Pongpat
fuente
1
¿Qué pasa si quiero mostrar DialogFragment dentro de otro fragmento?
desarrollador de Android
Nuestra solución es crear actividad y fragmentar la clase base y delegar onResumeFragments a fragment (creamos onResumeFragments en la clase base de fragment). No es una buena solución, pero funciona. Si tiene alguna solución mejor, hágamelo saber :)
Pongpat
Bueno, pensé que mostrar el cuadro de diálogo en "onStart" debería funcionar bien, ya que el fragmento seguramente se está mostrando, pero todavía veo algunos informes de fallas al respecto. Se me indicó que tratara de ponerlo en "onResume" en su lugar. Acerca de las alternativas, vi esto: twigstechtips.blogspot.co.il/2014/01/… , pero es bastante extraño.
desarrollador de Android
Creo que la razón por la que twigstechtips.blogspot.co.il/2014/01/… funciona porque inicia un nuevo hilo y, por lo tanto, todo el código del ciclo de vida, es decir, onStart, onResume, etc., se llama antes de que se ejecute el código runOnUiThread. Eso significa que el estado ya se restauró antes de que se llamara a runOnUiThread.
Pongpat
2
Uso una sola llamada para publicar (ejecutable). En cuanto a getFragmentManager, depende. Si desea compartir ese diálogo con otra actividad, debe usar getFragmentManager, sin embargo, si ese diálogo solo existe con el fragmento getChildFragmentManager parece una mejor opción.
Pongpat
16
private void showSnoozeDialog() {
    FragmentManager fm = getSupportFragmentManager();
    SnoozeDialog snoozeDialog = new SnoozeDialog();
    // snoozeDialog.show(fm, "snooze_dialog");
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    ft.add(snoozeDialog, "snooze_dialog");
    ft.commitAllowingStateLoss();
}

ref: enlace

huu duy
fuente
11

Después de unos días, quiero compartir mi solución cómo la arreglé, para mostrar DialogFragment, debe anular el show()método y llamar commitAllowingStateLoss()al Transactionobjeto. Aquí hay un ejemplo en Kotlin:

override fun show(manager: FragmentManager?, tag: String?) {
        try {
            val ft = manager?.beginTransaction()
            ft?.add(this, tag)
            ft?.commitAllowingStateLoss()
        } catch (ignored: IllegalStateException) {

        }

    }
Dennis Zinkovski
fuente
1
Por lo que los desarrolladores no tienen que heredar de DialogFragmentque podría cambiar esto a ser una función de extensión Kotlin con la firma siguiente: fun DialogFragment.showAllowingStateLoss(fragmentManager: FragmentManager, tag: String). Además, try-catch no es necesario ya que está llamando al commitAllowingStateLoss()método y no al commit()método.
Adil Hussain
10

Si el cuadro de diálogo no es realmente importante (está bien no mostrarlo cuando la aplicación se cerró / ya no está a la vista), use:

boolean running = false;

@Override
public void onStart() {
    running = true;
    super.onStart();
}

@Override
public void onStop() {
    running = false;
    super.onStop();
}

Y abre tu diálogo (fragmento) solo cuando estemos ejecutando:

if (running) {
    yourDialog.show(...);
}

EDITAR, PROBABLEMENTE MEJOR SOLUCIÓN:

Donde se llama a onSaveInstanceState en el ciclo de vida es impredecible, creo que una mejor solución es verificar isSavedInstanceStateDone () de esta manera:

/**
 * True if SavedInstanceState was done, and activity was not restarted or resumed yet.
 */
private boolean savedInstanceStateDone;

@Override
protected void onResume() {
    super.onResume();

    savedInstanceStateDone = false;
}

@Override
protected void onStart() {
    super.onStart();

    savedInstanceStateDone = false;
}

protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    savedInstanceStateDone = true;
}


/**
 * Returns true if SavedInstanceState was done, and activity was not restarted or resumed yet.
 */
public boolean isSavedInstanceStateDone() {
    return savedInstanceStateDone;
}
Franco
fuente
Esto no parece funcionar, ya que obtengo esta excepción en la llamada al método "onStart" (tratando de mostrar el DialogFragment allí).
desarrollador de Android
Salvaste mi día. Gracias Frank.
Cüneyt
7

Me he encontrado con este problema durante años.
Internet está plagado de decenas (¿cientos? ¿Miles?) De discusiones sobre esto, y la confusión y la desinformación en ellas parecen abundantes.
Para empeorar la situación, y en el espíritu del cómic xkcd "14 estándares", estoy lanzando mi respuesta al ring.
estándares xkcd 14

El cancelPendingInputEvents(), commitAllowingStateLoss(), catch (IllegalStateException e), y soluciones similares todos parecen atroz.

Con suerte, lo siguiente muestra fácilmente cómo reproducir y solucionar el problema:

private static final Handler sHandler = new Handler();
private boolean mIsAfterOnSaveInstanceState = true;

@Override
protected void onSaveInstanceState(Bundle outState)
{
    super.onSaveInstanceState(outState);
    mIsAfterOnSaveInstanceState = true; // <- To repro, comment out this line
}

@Override
protected void onPostResume()
{
    super.onPostResume();
    mIsAfterOnSaveInstanceState = false;
}

@Override
protected void onResume()
{
    super.onResume();
    sHandler.removeCallbacks(test);
}

@Override
protected void onPause()
{
    super.onPause();
    sHandler.postDelayed(test, 5000);
}

Runnable test = new Runnable()
{
    @Override
    public void run()
    {
        if (mIsAfterOnSaveInstanceState)
        {
            // TODO: Consider saving state so that during or after onPostResume a dialog can be shown with the latest text
            return;
        }

        FragmentManager fm = getSupportFragmentManager();
        DialogFragment dialogFragment = (DialogFragment) fm.findFragmentByTag("foo");
        if (dialogFragment != null)
        {
            dialogFragment.dismiss();
        }

        dialogFragment = GenericPromptSingleButtonDialogFragment.newInstance("title", "message", "button");
        dialogFragment.show(fm, "foo");

        sHandler.postDelayed(test, 5000);
    }
};
swooby
fuente
2
Amo a la gente que vota en contra sin explicación. En lugar de simplemente votar en contra, tal vez sería mejor si explicaran cómo mi solución es defectuosa ¿Puedo votar en contra de un votante en contra?
swooby
1
Sí, es un problema de SO, escribo este problema cada vez en sugerencias, pero no quieren resolverlo.
CoolMind
2
Creo que los votos negativos pueden ser el resultado del XKCD integrado, las respuestas realmente no son el lugar para los comentarios sociales (no importa cuán divertidas y / o verdaderas).
RestingRobot
6

intente utilizar FragmentTransaction en lugar de FragmentManager. Creo que el siguiente código resolverá tu problema. Si no es así, por favor hágamelo saber.

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
SnoozeDialog snoozeDialog = new SnoozeDialog();
snoozeDialog.show(ft, "snooze_dialog");

EDITAR:

Transacción de fragmentos

Por favor, consulte este enlace. Creo que te resolverá dudas.

RV RIJO
fuente
4
Cualquier explicación sobre por qué el uso de FragmentTransaction soluciona el problema sería genial.
Hemanshu
3
Dialog # show (FragmentManager, etiqueta) hace lo mismo. Esta no es una solución.
William
3
Esta respuesta no es la solución. DialogFragment # show (ft) y show (fm) hacen exactamente lo mismo.
danijoo
@danijoo Tienes razón en que ambos hacen el mismo trabajo. Pero en algunos teléfonos, existe algún problema similar a este si está utilizando fragmentmanager en lugar de fragmenttransaction. Entonces, en mi caso, esto resolvió mi problema.
RIJO RV
6

Usar los nuevos alcances del ciclo de vida de Activity-KTX es tan simple como el siguiente ejemplo de código:

lifecycleScope.launchWhenResumed {
   showErrorDialog(...)
}

Este método se puede llamar directamente después de onStop () y mostrará correctamente el diálogo una vez que se haya llamado a onResume () al regresar.

PenguinDan
fuente
3

Muchas vistas publican eventos de alto nivel, como controladores de clics, en la cola de eventos para que se ejecuten en diferido. Entonces, el problema es que "onSaveInstanceState" ya ha sido llamado para la actividad, pero la cola de eventos contiene un "evento de clic" diferido. Por lo tanto, cuando este evento se envía a su controlador

at android.os.Handler.handleCallback(Handler.java:605)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)

y su código hace que showse lanza la IllegalStateException.

La solución más simple es limpiar la cola de eventos, en onSaveInstanceState

protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        // ..... do some work
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            findViewById(android.R.id.content).cancelPendingInputEvents();
        }
}
sim
fuente
¿Realmente ha confirmado que esto resuelve el problema?
mhsmith
Google ha agregado esto a la próxima versión de las bibliotecas androidx, actualmente en versión beta ( activityy fragment).
mhsmith
1
@mhsmith Sí recuerdo que esta solución resolvió el problema en mi código con IllegalStateException
sim
2

Haga que su objeto de fragmento de diálogo sea global y llame a dispatsAllowingStateLoss () en el método onPause ()

@Override
protected void onPause() {
    super.onPause();

    if (dialogFragment != null) {
        dialogFragment.dismissAllowingStateLoss();
    }
}

No olvide asignar un valor en el fragmento y llamar a show () al hacer clic en el botón o donde sea.

Rohit Rajpal
fuente
1

Aunque no se menciona oficialmente en ninguna parte, me enfrenté a este problema un par de veces. En mi experiencia, hay algo mal en la biblioteca de compatibilidad que admite fragmentos en plataformas más antiguas que causa este problema. Puedes probar esto usando la API normal del administrador de fragmentos. Si nada funciona, puede usar el diálogo normal en lugar del fragmento de diálogo.

Dalvinder Singh
fuente
1
  1. Agregue esta clase a su proyecto: (debe estar en el paquete android.support.v4.app )
paquete android.support.v4.app;


/ **
 * Creado por Gil el 16/8/2017.
 * /

public class StatelessDialogFragment extiende DialogFragment {
    / **
     * Muestre el diálogo, agregue el fragmento usando una transacción existente y luego confirme el
     * transacción permitiendo la pérdida del estado.
* * Te recomendaría que uses {@link #show (FragmentTransaction, String)} la mayor parte del tiempo, pero * esto es para los diálogos que realmente no te interesan. (Depuración / Seguimiento / Anuncios, etc.) * * transacción @param * Una transacción existente en la que agregar el fragmento. * etiqueta @param * La etiqueta de este fragmento, según * {@link FragmentTransaction # add (Fragment, String) FragmentTransaction.add}. * @return Devuelve el identificador de la transacción comprometida, según * {@link FragmentTransaction # commit () FragmentTransaction.commit ()}. * @ver StatelessDialogFragment # showAllowingStateLoss (FragmentManager, String) * / public int showAllowingStateLoss (transacción FragmentTransaction, etiqueta String) { mDismissed = falso; mShownByMe = verdadero; transaccion.add (esto, etiqueta); mViewDestroyed = falso; mBackStackId = transaction.commitAllowingStateLoss (); return mBackStackId; } / ** * Muestra el diálogo, agregando el fragmento al FragmentManager dado. Esto es una conveniencia * para crear explícitamente una transacción, agregarle el fragmento con la etiqueta dada, y * cometiéndolo sin preocuparse por el estado. Esto no agrega la transacción al * pila posterior. Cuando se descarta el fragmento, se ejecutará una nueva transacción para eliminarlo * de la actividad.
* * Le recomendaría que utilice {@link #show (FragmentManager, String)} la mayor parte del tiempo, pero esto es * para los diálogos que realmente no le interesan. (Depuración / Seguimiento / Anuncios, etc.) * * * @param manager * El FragmentManager al que se agregará este fragmento. * etiqueta @param * La etiqueta de este fragmento, según * {@link FragmentTransaction # add (Fragment, String) FragmentTransaction.add}. * @ver StatelessDialogFragment # showAllowingStateLoss (FragmentTransaction, String) * / public void showAllowingStateLoss (administrador de FragmentManager, etiqueta de cadena) { mDismissed = falso; mShownByMe = verdadero; FragmentTransaction ft = manager.beginTransaction (); ft.add (esto, etiqueta); ft.commitAllowingStateLoss (); } }
  1. Extienda StatelessDialogFragment en lugar de DialogFragment
  2. Utilice el método showAllowingStateLoss en lugar de show

  3. Disfruta;)

Gil SH
fuente
¿Para qué sirven todos estos campos booleanos? ¿Por qué no se declaran como miembros de la clase?
indefinido
1
Los campos booleanos son miembros protegidos de DialogFragment, sus nombres obviamente sugieren para qué sirven y debemos actualizarlos para no interferir con la lógica de DialogFragment. Tenga en cuenta que en la clase DialogFragment original, estas funciones existen pero sin acceso público
Gil SH
Ough, estos miembros no están protegidos, son internos. Estaba obteniendo errores de compilación cuando puse StatelessDialogFragmentuno de mis paquetes. Gracias amigo. Lo probaré en producción pronto.
indefinido
1

usa este código

FragmentTransaction ft = fm.beginTransaction();
        ft.add(yourFragment, "fragment_tag");
        ft.commitAllowingStateLoss();

en vez de

yourFragment.show(fm, "fragment_tag");
Faxriddin Abdullayev
fuente
1

He encontrado una solución elegante para este problema utilizando la reflexión. El problema de todas las soluciones anteriores es que los campos mDismissed y mShownByMe no cambian su estado.

Simplemente anule el método "mostrar" en su propio fragmento de diálogo de hoja inferior personalizado como la muestra a continuación (Kotlin)

override fun show(manager: FragmentManager, tag: String?) {
        val mDismissedField = DialogFragment::class.java.getDeclaredField("mDismissed")
        mDismissedField.isAccessible = true
        mDismissedField.setBoolean(this, false)

        val mShownByMeField = DialogFragment::class.java.getDeclaredField("mShownByMe")
        mShownByMeField.isAccessible = true
        mShownByMeField.setBoolean(this, true)

        manager.beginTransaction()
                .add(this, tag)
                .commitAllowingStateLoss()
    }
Рома Богдан
fuente
4
"He encontrado una solución elegante para este problema utilizando la reflexión". como es elegante
Mark Buikema
elegante, elegante, elegante, inteligente, agradable, agraciado
Рома Богдан
1
es la única solución que funcionó para mí. Creo que es elegante
MBH
0

La siguiente implementación se puede utilizar para resolver el problema de realizar cambios de estado de forma segura durante el Activityciclo de vida, en particular para mostrar diálogos: si el estado de la instancia ya se ha guardado (por ejemplo, debido a un cambio de configuración), los pospone hasta que el estado reanudado haya realizado.

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);
    }
}

Luego, usando una clase como esta:

/** 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();
            }
        });
    }
}

Puede mostrar cuadros de diálogo de forma segura sin preocuparse por el estado de la aplicación:

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();
    }
}

y luego llame TestDialog.show(this)desde dentro de su XAppCompatActivity.

Si desea crear una clase de diálogo más genérica con parámetros, puede guardarlos Bundlecon los argumentos del show()método y recuperarlos con getArguments()in onCreateDialog().

Todo el enfoque puede parecer un poco complejo, pero una vez que haya creado las dos clases base para actividades y diálogos, es bastante fácil de usar y funciona perfectamente. Se puede utilizar para otras Fragmentoperaciones basadas que podrían verse afectadas por el mismo problema.

gicci
fuente
0

Este error parece estar ocurriendo porque los eventos de entrada (como los eventos de pulsación de tecla o al hacer clic) se entregan después de la onSaveInstanceStatellamada.

La solución es anular onSaveInstanceStatesu actividad y cancelar cualquier evento pendiente.

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        final View rootView = findViewById(android.R.id.content);
        if (rootView != null) {
            rootView.cancelPendingInputEvents();
        }
    }
}
Guillermo
fuente