Catégories

Je les lis (et ils le valent bien!)

Dév Android : les Dialog (problème de persistance du contenu avec onCreateDialog)

Logo AndroidJusque là pour créer mes Dialog (genre de popup) où afficher mes résultats, l’Aide ou encore le « à propos », j’utilisais uniquement un @override de onCreateDialog().
Sur mes Dialog je propose toujours un bouton pour le fermer. Quand j’ai fais mes premiers (pour Aide et A propos), j’avais placé sur ce bouton la méthode dialog.dismiss() qui marchait fort bien.

Quand j’ai voulu faire de même avec le Dialog de résultat je me suis retrouvé face à un problème : dialog.dismiss() ne supprime pas l’instance du Dialog. Cela se traduisait par le fait que les fois suivantes où j’appelais ce même Dialog, ce qu’il affichait était toujours ce qu’il avait affiché la première fois. Visiblement je ne passais plus dans le onCreateDialog().

En cherchant un peu j’ai trouvé qu’en utilisant removeDialog(id_du_Dialog) ça marchait correctement, c’est à dire que je passais à nouveau dans le onCreateDialog(), là où j’affecte le titre et le message que le Dialog doit présenter. Une bête erreur de copier-coller (les plus classiques en développement) m’a amené à creuser à nouveau la question. En effet, pour l’affichage des Logs sur lequel je travaille, j’utilise un Dialog. J’ai donc copié-collé mon bout de code du Dialog de résultat vu qu’ils devaient avoir le même genre de comportement. Et bêtement, j’avais oublié de mettre à jour l’id_du_Dialog en argument de removeDialog. Du coup, je n’arrivais pas à avoir une vue à jour du contenu du fichier de log. J’avais toujours que ce qu’il avait affiché à ma première instance. En cherchant le pourquoi de cette erreur ailleurs que sur le removeDialog, j’en suis donc venu à découvrir un peu mieux le fonctionnement d’un Dialog et aussi pourquoi il fallait que je modifie mon code sur le Dialog des résultats.

En fait il se trouve que dans le workflow (cycle de vie) d’un Dialog, on ne passe dans onCreateDialog que lors de la première instanciation.

dialog.dismiss() ne détruit pas l’instance, il se contente de ne plus l’afficher. A coté de ça removeDialog(id_du_Dialog) détruit effectivement l’instance (ce qui a pour effet de bord de ne plus l’afficher). C’est pour ça qu’avec removeDialog(id_du_Dialog) on repasse dans la méthode onCreateDialog lors d’un showDialog.

Sauf que… Instancier un nouveau Dialog est gourmand en ressources et il existe une méthode onPrepareDialog qui va tout le temps agir dans un showDialog et on peut l’utiliser pour donner un titre et un contenu (et d’autres choses certainement) à un Dialog. Reste à savoir comment…

Mon code qui utilisait le onCreateDialog uniquement :

protected Dialog onCreateDialog(int id, final Bundle bundle) {
	Dialog dialog = null;
	AlertDialog.Builder builder = new AlertDialog.Builder(this);
	Resources res = getResources();
	// Ici on peut définir plusieurs boites de dialogue différentes
	switch (id) {
		case DIALOG_CONSULT_LOG :
			// On définit le message à afficher dans la boite de dialogue
			builder.setMessage(bundle.getString("resultats"))
				.setTitle(res.getString(R.string.menu_logs))
				.setCancelable(false)
				.setNegativeButton(res.getString(R.string.ok),
					new DialogInterface.OnClickListener() {
						// On définit l'action pour le OK => on ferme le dialogue
						public void onClick(DialogInterface dialog, int id) {
						// On retire la boite de dialogue
						removeDialog(DIALOG_CONSULT_LOG);
					}
				})
				.setPositiveButton(res.getString(R.string.log_clear),
					new DialogInterface.OnClickListener() {
						// On définit l'action pour le log_clear => on supprime log.txt
						// Et on ferme le dialogue
						public void onClick(DialogInterface dialog, int id) {
							_clearLogs();
							// On retire la boite de dialogue
							removeDialog(DIALOG_CONSULT_LOG);
						}
					}
				);
				dialog = builder.create();
			break;
		}
		return dialog;
	}

L’idée est de réussir à effectuer le setMessage dans le onPrepareDialog… Une fois que ce sera réussi, on pourra remplacer le removeDialog(id_du_Dialog) par un dialog.dismiss();

Bon, première chose que je vois en étudiant onPrepareDialog :

This method is deprecated.
Use the new DialogFragment class with FragmentManager instead; this is also available on older platforms through the Android compatibility package.

Bon, par contre, je vois que DialogFragment est à partir de : API Level 11. Hors, en visant les Android 2.2, je suis sur l’API Level 8. Du coup, même si c’est deprecated (déprécié), je vais l’utiliser.

Par contre malheureusement ils ne donnent pas d’exemple de code. Trouvé sur http://stackoverflow.com/questions/5000776/how-to-change-the-messagebody-of-alertdialog-in-onpreparedialog.

/**
 * Met à jour le message du Dialog
 */
@Override
protected void onPrepareDialog (int id, Dialog dialog, Bundle bundle){
	switch (id) {
		case DIALOG_CONSULT_LOG :
			((AlertDialog) dialog).setMessage(bundle.getString("resultats"));
			break;
	}
}

Et l’astuce qu’il ne faut pas oublier : dans le onCreateDialog, il faut laisser un .setMessage(«  ») sinon, ça ne marche pas. Plus qu’à appliquer la même chose sur mes Dialog de résultat et à restester tout ça avant de pouvoir sortir la version 1.8 de SRDice.

Fini ! Ah bein non…

Evidemment quand je m’attaque au DIALOG_RESULT je tombe sur une nouvelle surprise : Dans la définition de ce dialog, donc dans le onCreateDialog, je positionne le bouton pour enregistrer le résultat et lui défini son action :

if (mExternalStorageWriteable == true) {
	// On a une carte SD, on peut proposer d'enregistrer le log
	builder.setPositiveButton(res.getString(R.string.logResult),
		new DialogInterface.OnClickListener() {
			// On définit l'action pour le Log => on enregistre dans les logs et on ferme le dialogue
			public void onClick(DialogInterface dialog, int id) {
				_saveResult(bundle);
				// On retire la boite de dialogue
				dialog.dismiss();
			}
		});
}

Malheureusement, du coup le bundle passé en argument est celui de la première création du Dialog (donc le premier lancer de Dés fait dans l’application) et il n’est pas mis à jour (vu que c’est dans le onPrepareDialog qu’on fait ça).

Il faut donc dans le onPrepareDialog redéfinir le Bouton :

if (mExternalStorageWriteable == true) {
	// On a une carte SD, on défini le bouton pour enregistrer le log
	((AlertDialog) dialog).setButton(DialogInterface.BUTTON_POSITIVE, res.getString(R.string.logResult),
		new DialogInterface.OnClickListener() {
			// On définit l'action pour le Log => on enregistre dans les logs et on ferme le dialogue
			public void onClick(DialogInterface dialog, int id) {
				_saveResult(bundle);
				// On retire la boite de dialogue
				dialog.dismiss();
			}
		}
	);
}

Pour « attraper » le bon bouton, il faut le récupérer avec DialogInterface. BUTTON_POSITIVE car dans le onCreateDialog j’en ai posé les bases avec builder.setPositiveButton.

(Dans un AlertDialog on peut définir 3 boutons : setPositiveButton, setNeutralButton et setNegativeButton qui apparaitrons les uns à cotés des autres dans cet ordre)

Du coup dans le onCreateDialog on peut faire le message pour ne présenter plus que ça :

if (mExternalStorageWriteable == true) {
	// On a une carte SD, on peut proposer d'enregistrer le log
	builder.setPositiveButton(res.getString(R.string.logResult),
		new DialogInterface.OnClickListener() {
			//On pose les bases, le listener réel est défini dans onPrepareDialog
			public void onClick(DialogInterface dialog, int id) {
			}
		});
}

Fin cette fois. On va sortir la V1.8

1 comment to Dév Android : les Dialog (problème de persistance du contenu avec onCreateDialog)

Leave a Reply

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

  

  

  

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.