Empêcher le rejet de la boîte de dialogue lors de la rotation de l'écran dans Android

94

J'essaie d'empêcher les boîtes de dialogue créées avec Alert Builder d'être fermées lorsque l'activité est redémarrée.

Si je surcharge la méthode onConfigurationChanged, je peux le faire avec succès et réinitialiser la mise en page pour une orientation correcte, mais je perds la fonction de texte collant d'edittext. Donc, en résolvant le problème de dialogue, j'ai créé ce problème edittext.

Si j'enregistre les chaînes de l'edittext et que je les réaffecte dans le changement onCofiguration, elles semblent toujours avoir par défaut la valeur initiale et non ce qui a été entré avant la rotation. Même si je force une invalidation semble les mettre à jour.

J'ai vraiment besoin de résoudre soit le problème de dialogue, soit le problème edittext.

Merci pour l'aide.

Draksia
la source
1
Comment enregistrer / restaurer le contenu du EditText édité? Pouvez-vous montrer du code?
Peter Knego du
J'ai compris le problème avec cela, j'oubliais de récupérer la vue par Id après avoir réinitialisé la mise en page.
draksia le

Réponses:

134

La meilleure façon d'éviter ce problème de nos jours est d'utiliser un fichier DialogFragment.

Créez une nouvelle classe qui s'étend DialogFragment. Remplacez onCreateDialoget renvoyez votre ancien Dialogou un fichier AlertDialog.

Ensuite, vous pouvez le montrer avec DialogFragment.show(fragmentManager, tag).

Voici un exemple avec un 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();
    }
}

Et dans l'activité que vous appelez:

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

Cette réponse aide à expliquer ces trois autres questions (et leurs réponses):

Brais Gabin
la source
Il y a un bouton dans mon application pour appeler .show (), je dois me souvenir de l'état de la boîte de dialogue d'alerte, montrant / ignoré. Existe-t-il un moyen de conserver la boîte de dialogue sans appeler .show ()?
Alpha Huang
1
Il dit que onAttachc'est obsolète maintenant. Que faut-il faire à la place?
farahm
3
@faraz_ahmed_kamran, vous devez utiliser onAttach(Context context)et android.support.v4.app.DialogFragment. La onAttachméthode prend la contextplace de activitycomme paramètre maintenant.
Stan Mots du
Il n'y en a probablement pas besoin YesNoListener. Voyez cette réponse .
Mygod le
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
la source
1
À qui trouve mon code inutile, essayez-le avant de cliquer :-)
Chung IW
6
Ça marche, je ne sais pas comment mais ça marche! Solution simple et abstraite propre, merci.
nmvictor
3
Je pense que ce code est mauvais. doLogout () a une référence au contexte qui est / contient l'activité. L'activité ne peut pas être détruite, ce qui peut provoquer une fuite de mémoire. Je cherchais une possibilité d'utiliser AlertDialog à partir d'un contexte statique mais maintenant je suis sûr que c'est impossible. Le résultat ne peut être que des ordures, je pense.
L'incroyable
2
On dirait que ça marche. La boîte de dialogue reste ouverte, mais elle n'a aucun lien avec l'activité ou le fragment nouvellement créé (elle est nouvellement créée à chaque changement d'orientation). Vous ne pouvez donc rien faire qui nécessite un Contextintérieur des boutons de la boîte de dialogue OnClickListener.
Udo Klimaschewski
2
Ce code fonctionne mais ne le recommande pas du tout. Il fuit une référence d'activité, c'est pourquoi le dialogue peut persister. C'est une très mauvaise pratique qui entraînera une fuite de mémoire.
Hexise le
4

Si vous modifiez la mise en page lors du changement d'orientation, je ne mettrais pas android:configChanges="orientation"dans votre manifeste parce que vous recréez les vues de toute façon.

Enregistrez l'état actuel de votre activité (comme le texte saisi, la boîte de dialogue affichée, les données affichées, etc.) en utilisant ces méthodes:

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

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

De cette façon, l'activité passe à nouveau par onCreate et appelle ensuite la méthode onRestoreInstanceState dans laquelle vous pouvez redéfinir votre valeur EditText.

Si vous souhaitez stocker des objets plus complexes, vous pouvez utiliser

@Override
public Object onRetainNonConfigurationInstance() {
}

Ici, vous pouvez stocker n'importe quel objet et dans onCreate, il vous suffit d'appeler getLastNonConfigurationInstance();pour obtenir l'objet.

Maria Neumayer
la source
4
OnRetainNonConfigurationInstance()est maintenant obsolète comme le dit le Doc: developer.android.com/reference/android/app/… setRetainInstance(boolean retain) devrait être utilisé à la place: developer.android.com/reference/android/app/…
ForceMagic
@ForceMagic setRetainInstanceest complètement différent: c'est pour les fragments et cela ne vous garantit pas que l'instance sera conservée.
Miha_x64
3

Ajoutez simplement android: configChanges = "orientation" avec votre élément d'activité dans AndroidManifest.xml

Exemple:

<activity
            android:name=".YourActivity"
            android:configChanges="orientation"
            android:label="@string/app_name"></activity>
SAAM
la source
Cela peut entraîner l'affichage incorrect de la boîte de dialogue dans certaines circonstances.
Sam
1
android: configChanges = "orientation | screenSize" Remarque: Si votre application cible Android 3.2 (niveau d'API 13) ou supérieur, vous devez également déclarer la configuration "screenSize", car elle change également lorsqu'un appareil bascule entre les orientations portrait et paysage.
tsig
1

Une approche très simple consiste à créer les boîtes de dialogue à partir de la méthode onCreateDialog()(voir la remarque ci-dessous). Vous leur montrez à travers showDialog(). De cette façon, Android gère la rotation pour vous et vous ne devez pas appeler dismiss()à onPause()éviter une WindowLeak et vous ne devez restaurer la boîte de dialogue. À partir de la documentation:

Afficher une boîte de dialogue gérée par cette activité. Un appel à onCreateDialog (int, Bundle) sera effectué avec le même identifiant la première fois qu'il est appelé pour un identifiant donné. À partir de là, la boîte de dialogue sera automatiquement enregistrée et restaurée.

Voir la documentation Android showDialog () pour plus d'informations. J'espère que cela aide quelqu'un!

Remarque: si vous utilisez AlertDialog.Builder, n'appelez pas show()depuis onCreateDialog(), appelez à la create()place. Si vous utilisez ProgressDialog, créez simplement l'objet, définissez les paramètres dont vous avez besoin et renvoyez-le. En conclusion, à l' show()intérieur onCreateDialog()cause des problèmes, créez simplement une instance de Dialog et renvoyez-la. Cela devrait fonctionner! (J'ai rencontré des problèmes lors de l'utilisation de showDialog () de onCreate () - qui n'affiche pas la boîte de dialogue -, mais si vous l'utilisez dans onResume () ou dans un rappel d'auditeur, cela fonctionne bien).

Caumons
la source
Dans quel cas auriez-vous besoin d'un code? Le onCreateDialog () ou le montrer avec le constructeur et appeler show () pour lui?
Caumons
J'ai réussi à le faire .. mais le fait est que onCreateDialog () est maintenant obsolète: - \
neteinstein
D'ACCORD! N'oubliez pas que la plupart des appareils Android fonctionnent toujours avec les versions 2.X, vous pouvez donc l'utiliser quand même! Jetez un œil à l' utilisation des versions de la plate-forme Android
Caumons
Pourtant, quelle est l'autre option sinon onCreateDialog?
neteinstein
1
Vous pouvez utiliser les classes de constructeur par exemple AlertDialog.Builder. Si vous l'utilisez à l'intérieur onCreateDialog(), au lieu d'utiliser, show()retournez le résultat de create(). Sinon, appelez show()et stockez le AlertDialog retourné dans un attribut de l'activité et dans onPause() dismiss()celui-ci s'il est affiché afin d'éviter un WindowLeak. J'espère que ça aide!
Caumons
1

Cette question a reçu une réponse il y a longtemps.

Pourtant, c'est une solution simple et non piratée que j'utilise pour moi-même.

J'ai fait cette classe d'aide pour moi-même, vous pouvez donc l'utiliser également dans votre application.

L'utilisation est:

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
la source
1

Vous pouvez combiner les méthodes onSave / onRestore de Dialog avec les méthodes onSave / onRestore de Activity pour conserver l'état du Dialog.

Remarque: Cette méthode fonctionne pour ces boîtes de dialogue "simples", telles que l'affichage d'un message d'alerte. Il ne reproduira pas le contenu d'une WebView intégrée dans une boîte de dialogue. Si vous voulez vraiment empêcher une boîte de dialogue complexe d'être rejetée pendant la rotation, essayez la méthode 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
la source
1

Certainement, la meilleure approche consiste à utiliser DialogFragment.

Voici ma solution de classe wrapper qui aide à empêcher que différentes boîtes de dialogue soient rejetées dans un fragment (ou une activité avec une petite refactorisation). En outre, cela permet d'éviter une refactorisation massive du code si, pour certaines raisons, il y en a beaucoup AlertDialogsdispersés dans le code avec de légères différences entre eux en termes d'actions, d'apparence ou autre.

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

En ce qui concerne l'activité, vous pouvez l'invoquer à l' getContext()intérieur onCreateDialog(), la lancer dans l' DialogProviderinterface et demander une boîte de dialogue spécifique par mDialogId. Toute logique de traitement d'un fragment cible doit être supprimée.

Utilisation à partir du fragment:

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

Vous pouvez lire l'article complet sur mon blog Comment éviter que Dialog ne soit rejeté? et jouez avec le code source .

Dmitry Korobeinikov
la source
1

Il semble que ce soit toujours un problème, même lorsque "tout faire correctement" et utiliser DialogFragmentetc.

Il existe un fil de discussion sur Google Issue Tracker qui prétend que cela est dû à un ancien message de rejet laissé dans la file d'attente des messages. La solution de contournement fournie est assez simple:

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

        super.onDestroyView();
    }

Incroyable que cela soit encore nécessaire 7 ans après le premier signalement de ce problème.

Magnus W
la source
Voir aussi stackoverflow.com/questions/14657490/… .
CoolMind le
0

J'ai eu un problème similaire: lorsque l'orientation de l'écran a changé, l' onDismissécouteur de la boîte de dialogue a été appelé même si l'utilisateur n'a pas fermé la boîte de dialogue. J'ai pu contourner ce problème en utilisant plutôt l' onCancelauditeur, qui s'est déclenché à la fois lorsque l'utilisateur a appuyé sur le bouton de retour et lorsque l'utilisateur a touché en dehors de la boîte de dialogue.

Sam
la source
-3

Juste utiliser

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

et l'application saura comment gérer la rotation et la taille de l'écran.

user3384694
la source