Impedir a dispensa da caixa de diálogo na rotação da tela no Android

94

Estou tentando evitar que os diálogos criados com o construtor Alert sejam descartados quando a atividade for reiniciada.

Se eu sobrecarregar o método onConfigurationChanged, posso fazer isso com êxito e redefinir o layout para a orientação correta, mas perco o recurso de texto fixo do edittext. Portanto, para resolver o problema do diálogo, criei este problema de edição de texto.

Se eu salvar as strings do texto de edição e reatribuí-las na alteração onCofiguration, elas ainda parecem ter como padrão o valor inicial, diferente do que foi inserido antes da rotação. Mesmo se eu forçar uma invalidação parece atualizá-los.

Eu realmente preciso resolver o problema de diálogo ou o problema de edição de texto.

Obrigado pela ajuda.

draksia
fonte
1
Como você salva / restaura o conteúdo do EditText editado? Você pode mostrar algum código?
Peter Knego
Eu descobri o problema com isso, eu estava esquecendo de obter a visão novamente pelo Id após redefinir o layout.
draksia

Respostas:

134

A melhor maneira de evitar esse problema hoje em dia é usando a DialogFragment.

Crie uma nova classe que estenda DialogFragment. Substitua onCreateDialoge devolva seu antigo Dialogou um AlertDialog.

Então você pode mostrar com DialogFragment.show(fragmentManager, tag).

Aqui está um exemplo com 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();
    }
}

E na Activity você chama:

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

Essa resposta ajuda a explicar essas outras três perguntas (e suas respostas):

Brais Gabin
fonte
Há um botão em meu aplicativo para chamar .show (), tenho que lembrar o estado da caixa de diálogo de alerta, mostrando / dispensado. Existe uma maneira de manter o diálogo sem chamar .show ()?
Alpha Huang
1
Diz que onAttachestá obsoleto agora. O que deve ser feito em vez disso?
farahm de
3
@faraz_ahmed_kamran, você deve usar onAttach(Context context)e android.support.v4.app.DialogFragment. O onAttachmétodo usa em contextvez de activityum parâmetro agora.
Stan Mots
Provavelmente não há necessidade disso YesNoListener. Veja esta resposta .
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
fonte
1
Para quem achar que meu código não é útil, experimente antes de clicar :-)
Chung IW
6
Funciona, não sei como mas funciona! Solução simples e abstrata limpa, obrigado.
nmvictor
3
Eu acho que esse código é ruim. doLogout () tem uma referência ao contexto que é / contém a atividade. A atividade não pode ser destruída, o que pode causar vazamento de memória. Eu estava procurando uma possibilidade de usar AlertDialog de um contexto estático, mas agora tenho certeza que é impossível. O resultado só pode ser lixo, eu acho.
O incrível janeiro
2
Parece que funciona. A caixa de diálogo permanece aberta, mas não tem conexão com a atividade ou fragmento recém-criado (é criada a cada mudança de orientação). Portanto, você não pode fazer nada que exija um Contextdentro dos botões de diálogo OnClickListener.
Udo Klimaschewski
2
Este código funciona, mas não é recomendável. Ele vaza referência de atividade, razão pela qual o diálogo pode ser persistente. Esta é uma prática muito ruim que pode causar vazamento de memória.
Hexise
4

Se você estiver mudando o layout na mudança de orientação, eu não colocaria android:configChanges="orientation"em seu manifesto porque você está recriando as visualizações de qualquer maneira.

Salve o estado atual de sua atividade (como texto inserido, caixa de diálogo exibida, dados exibidos etc.) usando estes métodos:

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

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

Dessa forma, a atividade passa por onCreate novamente e depois chama o método onRestoreInstanceState, onde você pode definir seu valor EditText novamente.

Se você deseja armazenar objetos mais complexos, você pode usar

@Override
public Object onRetainNonConfigurationInstance() {
}

Aqui você pode armazenar qualquer objeto e no onCreate basta chamar getLastNonConfigurationInstance();para pegar o objeto.

Maria Neumayer
fonte
4
OnRetainNonConfigurationInstance()agora está obsoleto como o Doc diz: developer.android.com/reference/android/app/… setRetainInstance(boolean retain) deve ser usado em seu lugar: developer.android.com/reference/android/app/…
ForceMagic
@ForceMagic setRetainInstanceé completamente diferente: é para Fragments e não garante que a instância será retida.
Miha_x64
3

Basta adicionar android: configChanges = "idance "com seu elemento de atividade em AndroidManifest.xml

Exemplo:

<activity
            android:name=".YourActivity"
            android:configChanges="orientation"
            android:label="@string/app_name"></activity>
SAAM
fonte
Isso pode fazer com que a caixa de diálogo seja exibida incorretamente em algumas circunstâncias.
Sam
1
android: configChanges = "idance | screenSize "Observação: se seu aplicativo for direcionado ao Android 3.2 (API de nível 13) ou superior, você também deve declarar a configuração" screenSize ", porque ela também muda quando um dispositivo alterna entre as orientações retrato e paisagem.
tsig
1

Uma abordagem muito fácil é criar os diálogos a partir do método onCreateDialog()(veja a nota abaixo). Você mostra a eles showDialog(). Desta forma, o Android lida com a rotação para você e você não tem que chamar dismiss()em onPause()para evitar uma WindowLeak e então você não tem que restaurar o diálogo. Dos documentos:

Mostra uma caixa de diálogo gerenciada por esta atividade. Uma chamada para onCreateDialog (int, Bundle) será feita com o mesmo id na primeira vez que for chamado para um determinado id. A partir daí, a caixa de diálogo será salva e restaurada automaticamente.

Consulte a documentação do Android showDialog () para obter mais informações. Espero que ajude alguém!

Nota: Se estiver usando AlertDialog.Builder, não chame show()de onCreateDialog(), chame em create()vez disso. Se estiver usando ProgressDialog, basta criar o objeto, definir os parâmetros necessários e retorná-lo. Concluindo, show()dentro onCreateDialog()causa problemas, basta criar a instância Dialog e retorná-la. Isso deve funcionar! (Tive problemas ao usar showDialog () de onCreate () - na verdade não mostrando a caixa de diálogo -, mas se você usá-lo em onResume () ou em um retorno de chamada do ouvinte, ele funciona bem).

Caumons
fonte
Para qual caso você precisaria de algum código? O onCreateDialog () ou exibi-lo com o construtor e chamar show () para ele?
Caumons de
Eu consegui fazer isso .. mas a questão é que onCreateDialog () agora está obsoleto: - \
neteinstein
ESTÁ BEM! Lembre-se de que a maioria dos dispositivos Android ainda funciona com as versões 2.X, então você pode usá-lo de qualquer maneira! Dê uma olhada no uso das versões da plataforma Android
Caumons
Ainda assim, qual é a outra opção senão onCreateDialog?
neteinstein
1
Você pode usar as classes de construtor, por exemplo AlertDialog.Builder. Se você usar dentro onCreateDialog(), em vez de usar, show()retorne o resultado de create(). show()Caso contrário , chame e armazene o AlertDialog retornado em um atributo da Activity e onPause() dismiss()nele se estiver mostrando para evitar um WindowLeak. Espero que ajude!
Caumons
1

Esta pergunta foi respondida há muito tempo.

No entanto, esta é uma solução simples e não hacky que eu uso para mim.

Fiz esta classe auxiliar para mim mesmo, então você também pode usá-la em seu aplicativo.

O uso é:

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

Ou

 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
fonte
1

Você pode combinar os métodos onSave / onRestore do Dialog com os métodos onSave / onRestore da Activity para manter o estado do Dialog.

Nota: Este método funciona para aqueles Diálogos "simples", como a exibição de uma mensagem de alerta. Ele não reproduz o conteúdo de um WebView embutido em um Diálogo. Se você realmente deseja evitar que um diálogo complexo seja descartado durante a rotação, tente o 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
fonte
1

Definitivamente, a melhor abordagem é usar DialogFragment.

Aqui está minha solução de classe wrapper que ajuda a evitar que diferentes diálogos sejam descartados dentro de um Fragment (ou Activity com pequena refatoração). Além disso, ajuda a evitar a refatoração massiva de código se, por alguns motivos, houver muitos dados AlertDialogsespalhados no código, com pequenas diferenças entre eles em termos de ações, aparência ou outra coisa.

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

Quando se trata de Activity, você pode invocar getContext()dentro onCreateDialog(), lançar para a DialogProviderinterface e solicitar um diálogo específico por mDialogId. Toda lógica para lidar com um fragmento de destino deve ser excluída.

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

Você pode ler o artigo completo no meu blog. Como evitar que o Dialog seja descartado? e brincar com o código-fonte .

Dmitry Korobeinikov
fonte
1

Parece que isso ainda é um problema, mesmo quando "está fazendo tudo certo" e usando DialogFragmentetc.

Há uma discussão no Google Issue Tracker que afirma que isso se deve a uma antiga mensagem de dispensa deixada na fila de mensagens. A solução alternativa fornecida é bastante simples:

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

        super.onDestroyView();
    }

Incrível que isso ainda seja necessário 7 anos depois que o problema foi relatado pela primeira vez.

Magnus W
fonte
Consulte também stackoverflow.com/questions/14657490/… .
CoolMind
0

Eu tive um problema semelhante: quando a orientação da tela mudou, o onDismissouvinte da caixa de diálogo foi chamado, embora o usuário não tenha dispensado a caixa de diálogo. Consegui contornar isso usando o onCancelouvinte, que disparou quando o usuário pressionou o botão Voltar e quando o usuário tocou fora da caixa de diálogo.

Sam
fonte
-3

Apenas use

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

e o aplicativo saberá como lidar com a rotação e o tamanho da tela.

user3384694
fonte