Evitar el cierre del cuadro de diálogo en la rotación de la pantalla en Android

94

Estoy tratando de evitar que los cuadros de diálogo creados con el generador de alertas se descarten cuando se reinicia la actividad.

Si sobrecargo el método onConfigurationChanged, puedo hacer esto con éxito y restablecer el diseño a la orientación correcta, pero pierdo la función de texto adhesivo de edittext. Entonces, al resolver el problema del diálogo, he creado este problema de edición de texto.

Si guardo las cadenas del texto de edición y las reasigno en el cambio onCofiguration, todavía parecen tener el valor inicial predeterminado, no el que se ingresó antes de la rotación. Incluso si fuerzo una invalidación, parece actualizarlos.

Realmente necesito resolver el problema del diálogo o el problema de editar texto.

Gracias por la ayuda.

draksia
fuente
1
¿Cómo se guarda / restaura el contenido del EditText editado? ¿Puedes mostrar algún código?
Peter Knego
Descubrí el problema con eso, me estaba olvidando de obtener la vista nuevamente por Id después de restablecer el diseño.
Draksia

Respuestas:

134

La mejor manera de evitar este problema hoy en día es utilizando un archivo DialogFragment.

Cree una nueva clase que se extienda DialogFragment. Anula onCreateDialogy devuelve tu antiguo Dialogo un AlertDialog.

Entonces puedes mostrarlo con DialogFragment.show(fragmentManager, tag).

Aquí tienes un ejemplo con Listener:

public class MyDialogFragment extends DialogFragment {

    public interface YesNoListener {
        void onYes();

        void onNo();
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        if (!(activity instanceof YesNoListener)) {
            throw new ClassCastException(activity.toString() + " must implement YesNoListener");
        }
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new AlertDialog.Builder(getActivity())
                .setTitle(R.string.dialog_my_title)
                .setMessage(R.string.dialog_my_message)
                .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        ((YesNoListener) getActivity()).onYes();
                    }
                })
                .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        ((YesNoListener) getActivity()).onNo();
                    }
                })
                .create();
    }
}

Y en la Actividad llamas:

new MyDialogFragment().show(getSupportFragmentManager(), "tag"); // or getFragmentManager() in API 11+

Esta respuesta ayuda a explicar estas otras tres preguntas (y sus respuestas):

Brais Gabin
fuente
Hay un botón en mi aplicación para llamar a .show (), tengo que recordar el estado del diálogo de alerta, mostrando / descartado. ¿Hay alguna forma de mantener el diálogo sin llamar a .show ()?
Alpha Huang
1
Dice que onAttachahora está en desuso. ¿Qué debería hacerse en su lugar?
farahm
3
@faraz_ahmed_kamran, debes usar onAttach(Context context)y android.support.v4.app.DialogFragment. El onAttachmétodo toma ahora como parámetro en contextlugar de activity.
Stan Mots
Sin YesNoListenerembargo , probablemente no sea necesario . Vea esta respuesta .
Mygod
46
// Prevent dialog dismiss when orientation changes
private static void doKeepDialog(Dialog dialog){
    WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
    lp.copyFrom(dialog.getWindow().getAttributes());
    lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
    lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
    dialog.getWindow().setAttributes(lp);
}
public static void doLogout(final Context context){     
        final AlertDialog dialog = new AlertDialog.Builder(context)
        .setIcon(android.R.drawable.ic_dialog_alert)
        .setTitle(R.string.titlelogout)
        .setMessage(R.string.logoutconfirm)
        .setPositiveButton("Yes", new DialogInterface.OnClickListener()
        {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                ...   
            }

        })
        .setNegativeButton("No", null)      
        .show();    

        doKeepDialog(dialog);
    }
Chung IW
fuente
1
Para quien encuentre que mi código no es útil, pruébelo antes de hacer clic :-)
Chung IW
6
¡Funciona, no sé cómo pero funciona! Solución limpia simple y abstracta, gracias.
nmvictor
3
Creo que este código es malo. doLogout () tiene una referencia al contexto que es / contiene la actividad. La actividad no se puede destruir, lo que puede provocar una pérdida de memoria. Estaba buscando la posibilidad de usar AlertDialog desde un contexto estático, pero ahora estoy seguro de que es imposible. Creo que el resultado solo puede ser basura.
El increíble
2
Simplemente parece que funciona. El cuadro de diálogo permanece abierto, pero no tiene conexión con la actividad o el fragmento recién creado (se crea de nuevo en cada cambio de orientación). Por lo tanto, no puede hacer nada que requiera un Contextdentro de los botones de diálogo OnClickListener.
Udo Klimaschewski
2
Este código funciona pero no se recomienda en absoluto. Filtra la referencia de actividad, por lo que el diálogo puede persistir. Esta es una práctica muy mala que provocará pérdidas de memoria.
Hexise
4

Si está cambiando el diseño en el cambio de orientación, no lo pondría android:configChanges="orientation"en su manifiesto porque está recreando las vistas de todos modos.

Guarde el estado actual de su actividad (como el texto ingresado, el diálogo mostrado, los datos mostrados, etc.) utilizando estos métodos:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
}

De esa manera, la actividad pasa por onCreate nuevamente y luego llama al método onRestoreInstanceState donde puede establecer su valor EditText nuevamente.

Si desea almacenar objetos más complejos, puede usar

@Override
public Object onRetainNonConfigurationInstance() {
}

Aquí puede almacenar cualquier objeto y en onCreate solo tiene que llamar getLastNonConfigurationInstance();para obtener el objeto.

María Neumayer
fuente
4
OnRetainNonConfigurationInstance()ahora está en desuso como dice el documento: developer.android.com/reference/android/app/… setRetainInstance(boolean retain) debería usarse en su lugar: developer.android.com/reference/android/app/…
ForceMagic
@ForceMagic setRetainInstancees completamente diferente: es para Fragments y no le garantiza que se retendrá la instancia.
Miha_x64
3

Simplemente agregue android: configChanges = "Orientación" con su elemento de actividad en AndroidManifest.xml

Ejemplo:

<activity
            android:name=".YourActivity"
            android:configChanges="orientation"
            android:label="@string/app_name"></activity>
SAAM
fuente
Esto puede hacer que el cuadro de diálogo se muestre incorrectamente en algunas circunstancias.
Sam
1
android: configChanges = "Orientación | ScreenSize" Nota: Si su aplicación apunta a Android 3.2 (API nivel 13) o superior, también debe declarar la configuración "screenSize", porque también cambia cuando un dispositivo cambia entre orientación vertical y horizontal.
tsig
1

Un enfoque muy sencillo es crear los diálogos a partir del método onCreateDialog()(consulte la nota a continuación). Les muestras a través showDialog(). De esta manera, Android se encarga de la rotación para usted y usted no tiene que llamar dismiss()en onPause()evitar una WindowLeak y luego no tener que restaurar el diálogo. De los documentos:

Muestra un cuadro de diálogo gestionado por esta actividad. Se realizará una llamada a onCreateDialog (int, Bundle) con el mismo ID la primera vez que se llame para un ID determinado. A partir de entonces, el cuadro de diálogo se guardará y restaurará automáticamente.

Consulte los documentos de Android showDialog () para obtener más información. ¡Espero que ayude a alguien!

Nota: Si usa AlertDialog.Builder, no llame show()desde onCreateDialog(), llame en su create()lugar. Si usa ProgressDialog, simplemente cree el objeto, establezca los parámetros que necesita y devuélvalo. En conclusión, show()inside onCreateDialog()causa problemas, simplemente cree la instancia de Dialog y devuélvala. ¡Esto debería funcionar! (He tenido problemas al usar showDialog () desde onCreate () -en realidad no muestra el diálogo-, pero si lo usa en onResume () o en una devolución de llamada de oyente, funciona bien).

Caumons
fuente
¿Para qué caso necesitaría algún código? ¿El onCreateDialog () o mostrarlo con el constructor y llamar a show ()?
Caumons
Me las arreglé para hacerlo ... pero la cosa es que onCreateDialog () ahora está en desuso: - \
neteinstein
¡OKAY! Tenga en cuenta que la mayoría de los dispositivos Android todavía funcionan con versiones 2.X, por lo que puede usarlo de todos modos. Eche un vistazo al uso de las versiones de la plataforma Android
Caumons
Aún así, ¿cuál es la otra opción si no es onCreateDialog?
neteinstein
1
Puede utilizar las clases de constructor, por ejemplo AlertDialog.Builder. Si lo usa en el interior onCreateDialog(), en lugar de usar, show()devuelva el resultado de create(). De lo contrario, llame show()y almacene el AlertDialog devuelto en un atributo de la Actividad y en onPause() dismiss()él si se muestra para evitar un WindowLeak. ¡Espero eso ayude!
Caumons
1

Esta pregunta fue respondida hace mucho tiempo.

Sin embargo, esta es una solución simple y no pirateada que uso para mí.

Hice esta clase de ayuda por mí mismo, por lo que también puede usarla en su aplicación.

El uso es:

PersistentDialogFragment.newInstance(
        getBaseContext(),
        RC_REQUEST_CODE,
        R.string.message_text,
        R.string.positive_btn_text,
        R.string.negative_btn_text)
        .show(getSupportFragmentManager(), PersistentDialogFragment.TAG);

O

 PersistentDialogFragment.newInstance(
        getBaseContext(),
        RC_EXPLAIN_LOCATION,
        "Dialog title", 
        "Dialog Message", 
        "Positive Button", 
        "Negative Button", 
        false)
    .show(getSupportFragmentManager(), PersistentDialogFragment.TAG);





public class ExampleActivity extends Activity implements PersistentDialogListener{

        @Override
        void onDialogPositiveClicked(int requestCode) {
                switch(requestCode) {
                  case RC_REQUEST_CODE:
                  break;
                }
        }

        @Override
        void onDialogNegativeClicked(int requestCode) {
                switch(requestCode) {
                  case RC_REQUEST_CODE:
                  break;
                }          
        }
}
Ioane Sharvadze
fuente
1

Puede combinar los métodos onSave / onRestore de Dialog con los métodos onSave / onRestore de Activity para mantener el estado del Dialog.

Nota: este método funciona para esos cuadros de diálogo "simples", como mostrar un mensaje de alerta. No reproducirá el contenido de un WebView incrustado en un diálogo. Si realmente desea evitar que se elimine un diálogo complejo durante la rotación, pruebe el método de Chung IW.

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
     super.onRestoreInstanceState(savedInstanceState);
     myDialog.onRestoreInstanceState(savedInstanceState.getBundle("DIALOG"));
     // Put your codes to retrieve the EditText contents and 
     // assign them to the EditText here.
}

@Override
protected void onSaveInstanceState(Bundle outState) {
     super.onSaveInstanceState(outState);
     // Put your codes to save the EditText contents and put them 
     // to the outState Bundle here.
     outState.putBundle("DIALOG", myDialog.onSaveInstanceState());
}
hackjutsu
fuente
1

Definitivamente, el mejor enfoque es usar DialogFragment.

Aquí está la solución mía de la clase contenedora que ayuda a evitar que se descarten diferentes diálogos dentro de un Fragmento (o Actividad con pequeña refactorización). Además, ayuda a evitar la refactorización masiva de código si, por alguna razón, hay muchos AlertDialogscódigos dispersos con ligeras diferencias entre ellos en términos de acciones, apariencia o algo más.

public class DialogWrapper extends DialogFragment {
    private static final String ARG_DIALOG_ID = "ARG_DIALOG_ID";

    private int mDialogId;

    /**
     * Display dialog fragment.
     * @param invoker  The fragment which will serve as {@link AlertDialog} alert dialog provider
     * @param dialogId The ID of dialog that should be shown
     */
    public static <T extends Fragment & DialogProvider> void show(T invoker, int dialogId) {
        Bundle args = new Bundle();
        args.putInt(ARG_DIALOG_ID, dialogId);
        DialogWrapper dialogWrapper = new DialogWrapper();
        dialogWrapper.setArguments(args);
        dialogWrapper.setTargetFragment(invoker, 0);
        dialogWrapper.show(invoker.getActivity().getSupportFragmentManager(), null);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mDialogId = getArguments().getInt(ARG_DIALOG_ID);
    }

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return getDialogProvider().getDialog(mDialogId);
    }

    private DialogProvider getDialogProvider() {
        return (DialogProvider) getTargetFragment();
    }

    public interface DialogProvider {
        Dialog getDialog(int dialogId);
    }
}

Cuando se trata de Actividad, puede invocarlo getContext()dentro onCreateDialog(), enviarlo a la DialogProviderinterfaz y solicitar un diálogo específico por mDialogId. Se debe eliminar toda la lógica para tratar con un fragmento de destino.

Uso del fragmento:

public class MainFragment extends Fragment implements DialogWrapper.DialogProvider {
    private static final int ID_CONFIRMATION_DIALOG = 0;

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        Button btnHello = (Button) view.findViewById(R.id.btnConfirm);
        btnHello.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                DialogWrapper.show(MainFragment.this, ID_CONFIRMATION_DIALOG);
            }
        });
    }

    @Override
    public Dialog getDialog(int dialogId) {
        switch (dialogId) {
            case ID_CONFIRMATION_DIALOG:
                return createConfirmationDialog(); //Your AlertDialog
            default:
                throw new IllegalArgumentException("Unknown dialog id: " + dialogId);
        }
    }
}

Puedes leer el artículo completo en mi blog ¿Cómo evitar que se cierre el diálogo? y juega con el código fuente .

Dmitry Korobeinikov
fuente
1

Parece que esto sigue siendo un problema, incluso cuando se "hace todo bien" y se usa, DialogFragmentetc.

Hay un hilo en Google Issue Tracker que afirma que se debe a que se ha dejado un mensaje de descarte antiguo en la cola de mensajes. La solución alternativa proporcionada es bastante simple:

    @Override
    public void onDestroyView() {
        /* Bugfix: https://issuetracker.google.com/issues/36929400 */
        if (getDialog() != null && getRetainInstance())
            getDialog().setDismissMessage(null);

        super.onDestroyView();
    }

Es increíble que esto siga siendo necesario 7 años después de que se informó por primera vez de ese problema.

Magnus W
fuente
Consulte también stackoverflow.com/questions/14657490/… .
CoolMind
0

Tuve un problema similar: cuando cambió la orientación de la pantalla, onDismissse llamó al oyente del cuadro de diálogo aunque el usuario no descartó el cuadro de diálogo. Pude solucionar esto utilizando en su lugar el onCanceloyente, que se activaba tanto cuando el usuario presionaba el botón Atrás como cuando el usuario tocaba fuera del cuadro de diálogo.

Sam
fuente
-3

Solo usa

ConfigurationChanges = Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.ScreenSize

y la aplicación sabrá cómo manejar la rotación y el tamaño de la pantalla.

usuario3384694
fuente