Étape 1 : création d'un nouveau projet AIR▲
La première chose à faire est d'initialiser un nouveau projet AIR en utilisant le menu suivant :
File > New > Flex Project
Sélectionnez ensuite l'option application de bureau AIR (Desktop application) puis validez.
Le dossier source de l'application créée contient le fichier mxml avec la définition de l'interface principale de l'application :
<?xml version="1.0" encoding="utf-8"?>
<
mx
:
WindowedApplication
xmlns
:
mx
=
"http://www.adobe.com/2006/mxml"
layout
=
"vertical"
>
</
mx
:
WindowedApplication>
Le projet sera organisé avec les trois packages suivants : BO pour les objets métiers (ici une seule classe Contact), DB pour les classes d'accès aux données de la base et GUI qui contiendra les interfaces de l'application.
Étape 2 : définition de l'objet Contact▲
Pour commencer il faut définir notre contact. Les propriétés d'un contact sont un pseudonyme, une date de naissance, une adresse mail et un avatar.
Pour stocker l'avatar on utilise une variable de type ByteArray. La propriété ID correspondra à un identifiant auto-incrémenté dans la base de données.
package
BO
{
import
flash.
utils.
ByteArray;
public
class
Contact
{
public
var
id:
int
;
public
var
pseudo:
String
;
public
var
dateNaissance:
Date
;
public
var
mail:
String
;
public
var
avatar:
ByteArray;
public
function
Contact
(
)
{
ID =
-
1
;
Pseudo =
""
;
Mail =
""
;
DateNaissance =
null
;
Avatar =
null
;
}
public
function
get
ID
(
):
int
{
return
id;
}
public
function
set ID
(
pValue:
int
):
void
{
id =
pValue;
}
public
function
get
Pseudo
(
):
String
{
return
pseudo;
}
public
function
set Pseudo
(
pValue:
String
):
void
{
pseudo =
pValue;
}
public
function
get
Mail
(
):
String
{
return
mail;
}
public
function
set Mail
(
pValue:
String
):
void
{
mail =
pValue;
}
public
function
get
Avatar
(
):
ByteArray
{
return
avatar;
}
public
function
set Avatar
(
pValue:
ByteArray):
void
{
avatar =
pValue;
}
public
function
get
DateNaissance
(
):
Date
{
return
dateNaissance;
}
public
function
set DateNaissance
(
pDate:
Date
):
void
{
dateNaissance =
pDate;
}
}
}
Étape 3 : initialisation des classes d'accès aux données▲
Il faut définir une interface d'accès à notre base de contacts qui fournira les fonctionnalités suivantes : la sélection des contacts de la base, l'insertion, la mise à jour et la suppression de contacts.
Nous allons créer une classe Database dans le package DB pour effectuer l'exécution des requêtes.
Nous devrons donc gérer au sein de cette classe le fichier de la base, la connexion à la base, l'initialisation et l'exécution des objets SQLStatement.
Il est aussi nécessaire de gérer les erreurs lors de l'exécution des requêtes et de permettre de récupérer la description d'une éventuelle erreur.
Afin d'alléger un peu le code, la classe Database héritera d'une classe Logger, qui permettra de stocker et de mettre à disposition la description des erreurs survenues.
La classe Logger :
package
DB
{
import
flash.
events.
EventDispatcher;
public
class
Logger extends
EventDispatcher
{
private
var
errorMessageList:
Array
;
public
function
get
LastErrorMessage
(
):
String
{
return
errorMessageList.
length
>
0
?
errorMessageList[
errorMessageList.
length
-
1
]
:
"No error"
;
}
public
function
Logger
(
)
{
errorMessageList =
new
Array
(
);
}
protected
function
logError
(
pErrorMessage:
String
):
void
{
errorMessageList.
push
(
pErrorMessage);
trace
(
pErrorMessage);
}
}
}
La classe Database est uniquement initialisée (voir ci-desous), les différentes méthodes nécessaires seront rajoutées au moment voulu.
La classe Database possède donc une référence vers le fichier de la base qui est initialisée dans le constructeur et qui ne changera pas.
package
DB
{
import
flash.
filesystem.
File;
import
flash.
data
.
SQLConnection;
import
flash.
data
.
SQLStatement;
public
class
Database extends
Logger
{
private
var
databaseFile:
File;
public
function
Database
(
)
{
databaseFile =
File.
applicationStorageDirectory.
resolvePath
(
"database.sqlite"
);
}
}
}
Pour pouvoir accéder facilement à la base de contacts dans le reste de l'application, la classe Database sera un Singleton (une instance unique accessible de façon statique).
L'instance est accessible avec la propriété Database.Instance.
package
DB
{
import
flash.
filesystem.
File;
public
class
Database extends
Logger
{
private
static
var
constructorKey:
Object
=
{};
private
static
var
instance:
Database =
null
;
private
var
databaseFile:
File;
public
function
Database
(
pConstructorKey:
Object
)
{
if
(
pConstructorKey !=
constructorKey)
{
throw
new
Error
(
"Illegal instanciation (private constructor)"
);
}
databaseFile =
File.
applicationStorageDirectory.
resolvePath
(
"database.sqlite"
);
}
public
static
function
get
Instance
(
):
Database
{
if
(
instance ==
null
)
{
instance =
new
Database
(
constructorKey);
}
return
instance;
}
}
}
Étape 4 : initialisation de la base de données▲
Lors de l'initialisation de l'application il faut vérifier si la base existe et la créer dans le cas contraire. Nous créons donc une méthode createDatabase qui retourne un booléen pour indiquer si la création s'est bien déroulée.
public
function
createDatabase
(
):
Boolean
{
if
(
databaseFile.
exists)
{
return
true
;
}
// la suite ici
}
Premièrement il faut construire la requête de création de la table Contact. Il faut s'assurer que les noms des champs de la table correspondent aux noms des propriétés de l'objet Contact créé précédemment. La requête est la suivante :
CREATE
TABLE
Contact (
ID INTEGER
PRIMARY
KEY
, Pseudo TEXT
, Mail TEXT
, DateNaissance Date
, Avatar OBJECT )
Nous devons maintenant créer une méthode pour exécuter la requête, méthode qui sera privée car uniquement à utiliser dans cette classe. Elle assure l'ouverture et la fermeture de la connexion, la définition d'une variable SQLStatement et son exécution puis retourne le résultat de la requête. Si une erreur se produit, la valeur null est retournée.
private
function
executeQuery
(
pMode:
SQLMode,
pQuery:
String
):
SQLResult
{
try
{
var
Connection:
SQLConnection =
new
SQLConnection
(
);
var
Statement:
SQLStatement =
new
SQLStatement
(
);
Connection.
open
(
databaseFile,
pMode);
Statement.
sqlConnection =
Connection;
Statement.
text
=
pQuery;
Statement.
execute
(
);
return
Statement.
getResult
(
);
}
catch
(
error:
SQLError)
{
this
.
logError
(
"Requête : <"
+
pQuery +
">\n"
+
error.
toString
(
));
}
finally
{
Connection.
close
(
);
}
return
null
;
}
La méthode de création est donc la suivante :
public
function
createDatabase
(
):
Boolean
{
if
(
databaseFile.
exists)
{
return
true
;
}
var
sQuery:
String
=
"CREATE TABLE Contact ("
;
sQuery +=
"ID INTEGER PRIMARY KEY, "
sQuery +=
"Pseudo TEXT, "
sQuery +=
"Mail TEXT, "
sQuery +=
"DateNaissance Date, "
sQuery +=
"Avatar OBJECT "
sQuery +=
")"
;
var
result:
SQLResult =
this
.
executeCreateQuery
(
SQLMode.
CREATE,
sQuery);
return
result !=
null
;
}
Il reste à appeler cette méthode lors de l'initialisation de l'interface principale, pour cela il faut définir l'évènement creationComplete de l'application dans le fichier principal (TutoDVP.mxml) :
<?
xml version
=
"1.0"
encoding=
"utf-8"
?>
<
mx:
WindowedApplication xmlns:
mx=
"http://www.adobe.com/2006/mxml"
layout=
"vertical"
creationComplete=
"InitApplication()"
width
=
"500"
height
=
"470"
title=
"Tutoriel AIR SQLite developpez"
viewSourceURL=
"srcview/index.html"
>
<
mx:
Script>
<![
CDATA[
import
mx.
controls.
Alert;
import
DB.
Database;
private
function
initApplication
(
):
void
{
if
(!
Database.
Instance.
createDatabase
(
))
{
Alert.
show
(
"Erreur lors de la création de la base :\n"
+
Database.
Instance.
LastErrorMessage,
"Tutoriel SQLite developpez"
);
}
else
{
Alert.
show
(
"Base créée."
,
"Tutoriel SQLite developpez"
);
}
}
]
]>
</
mx:
Script>
</
mx:
WindowedApplication>
Vous pouvez déjà lancer l'application pour créer la base !
On peut ensuite vérifier que la table est bien créée à l'aide d'un utilitaire qui permet d'explorer les bases SQLite :
Étape 5 : création de l'interface pour l'ajout d'un contact▲
Maintenant que notre base de données est créée il faut y insérer des données. Nous allons créer une fenêtre contenant un formulaire pour la création d'un nouveau contact. Cette fenêtre sera un composant basé sur un objet TitleWindow. Pour le créer, sélectionnez le menu correspondant, puis donnez un nom et une classe de base comme ci-dessous :
Cette fenêtre va contenir un formulaire comprenant trois champs de saisie pour le pseudonyme, l'adresse mail et la date de naissance, puis une image avec un bouton pour sélectionner l'avatar.
Lorsque le formulaire est rempli et validé, un objet Contact est créé à partir des différents éléments.
<?xml version="1.0" encoding="utf-8"?>
<
mx
:
TitleWindow
xmlns
:
mx
=
"http://www.adobe.com/2006/mxml"
layout
=
"vertical"
width
=
"450"
height
=
"350"
title
=
"Nouveau contact"
>
<
mx
:
Script>
<![CDATA[
import mx.controls.Alert;
import mx.managers.DragManager;
import mx.graphics.codec.JPEGEncoder;
import mx.validators.Validator;
import mx.managers.PopUpManager;
import BO.Contact;
public function closeWindow():void
{
PopUpManager.removePopUp(this);
}
public function validateForm():void
{
var validators:Array = Validator.validateAll([validMail, validPseudo, validDate]);
if(!validators.length)
{
// Formulaire valide
var co:Contact = new Contact();
co.Pseudo = inputPseudo.text;
co.Mail = inputMail.text;
co.DateNaissance = inputDate.selectedDate;
co.Avatar = imageToByteArray(inputAvatar);
trace(co);
}
}
private function imageToByteArray(img:Image):ByteArray
{
var bmd:BitmapData = new BitmapData(img.width, img.height, true);
bmd.draw(img, new Matrix());
var bm:Bitmap = new Bitmap(bmd);
var jpg:JPEGEncoder = new JPEGEncoder();
return jpg.encode(bmd);
}
private function selectImage():void
{
var imagesFilter:FileFilter = new FileFilter("Images", "*.jpg;*.gif;*.png;*.swf;");
var imageFile:File = new File();
imageFile.browseForOpen("Selectionnez une image", [imagesFilter]);
imageFile.addEventListener(Event.SELECT, imageSelected);
}
private function imageSelected(pEvent:Event):void
{
inputAvatar.source = (pEvent.currentTarget as File).url;
}
] ]>
</mx:Script>
<mx:StringValidator id="validPseudo" source="{inputPseudo}" property="text" trigger="{btnValidate}"
triggerEvent="click" minLength="2" maxLength="20"
tooShortError="2 caractères minimum !" tooLongError="Pseudonyme trop long !"
requiredFieldError="Veuillez indiquer un Pseudonyme." />
<mx:EmailValidator id="validMail" source="{inputMail}" property="text" trigger="{btnValidate}"
triggerEvent="click"
requiredFieldError="Veuillez indiquer une adresse mail."
missingAtSignError="Le charactere @ est manquant dans votre adresse email."
missingPeriodInDomainError="Le domaine est manquant dans votre adresse email."
missingUsernameError="Le nom d'utilisateur est manquant dans votre adresse email." />
<mx:DateValidator id="validDate" source="{inputDate}" property="text" trigger="{btnValidate}"
triggerEvent="click" allowedFormatChars="/" inputFormat="DD/MM/YYYY"
requiredFieldError="Entrez la date de naissance" />
<mx:Form width="100%">
<mx:FormItem label="Pseudo">
<mx:TextInput id="inputPseudo" />
</mx:FormItem>
<mx:FormItem label="Adresse mail">
<mx:TextInput id="inputMail" />
</mx:FormItem>
<mx:FormItem label="Date de naissance">
<mx:DateField id="inputDate" editable="true" formatString="DD/MM/YYYY" />
</mx:FormItem>
<mx:FormItem label="Avatar">
<mx:HBox id="boxAvatar">
<mx:Canvas borderThickness="2" borderStyle="solid">
<mx:Image id="inputAvatar" scaleContent="{ckScale.selected}" maintainAspectRatio="true" width="170" height="130" />
</mx:Canvas>
<mx:VBox>
<mx:Button label="Parcourir" click="selectImage()" />
<mx:CheckBox id="ckScale" selected="true" label="Ajuster" />
</mx:VBox>
</mx:HBox>
</mx:FormItem>
</mx:Form>
<mx:HBox width="100%" horizontalAlign="center">
<mx:Spacer width="100%" />
<mx:Button label="Annuler" click="closeWindow()" />
<mx:Spacer width="30%" />
<mx:Button id="btnValidate" label="Valider" click="validateForm()" />
<mx:Spacer width="100%" />
</mx:HBox>
</mx:TitleWindow>
Dans l'interface principale on ajoute un bouton "Nouveau contact" qui va ouvrir la fenêtre de création de contacts :
<
mx
:
HBox>
<
mx
:
Spacer
width
=
"100%"
/>
<
mx
:
Button
label
=
"Nouveau contact"
horizontalCenter
=
"true"
click
=
"addContact()"
/>
<
mx
:
Spacer
width
=
"100%"
/>
</
mx
:
HBox>
avec la fonction addContact qui utilise la classe PopUpManager :
private
function
addContact
(
):
void
{
var
window:
IFlexDisplayObject =
PopUpManager.
createPopUp
(
this
,
ContactWindow ,
true
);
var
CreationWindow:
ContactWindow =
ContactWindow
(
window);
PopUpManager.
centerPopUp
(
CreationWindow);
}
L'interface est maintenant créée, lancez l'application pour vérifier que la fenêtre réagit bien.
Bien sûr il faut maintenant créer la fonction d'insertion du contact dans la base de données.
Étape 6 : insérer un nouveau contact▲
Pour l'insertion du nouveau contact nous allons créer la méthode correspondante dans la classe Database. Comme pour la création de la base il faut d'abord construire la requête puis l'exécuter. Nous devons en plus récupérer le nouvel identifiant automatique généré.
L'exécution de cette requête nécessite la définition de paramètres, il faut donc enrichir la méthode executeQuery avec un nouveau paramètre pParameters de type Object. Ce paramètre aura la valeur null par défaut pour ne pas modifier le comportement initial de la méthode.
private
function
executeQuery
(
pMode:
String
,
pQuery:
String
,
pParameters:
Object
=
null
):
SQLResult
{
try
{
var
Connection:
SQLConnection =
new
SQLConnection
(
);
var
Statement:
SQLStatement =
new
SQLStatement
(
);
Connection.
open
(
databaseFile,
pMode);
Statement.
sqlConnection =
Connection;
Statement.
text
=
pQuery;
if
(
pParameters !=
null
)
{
for
(
var
id:
String
in
pParameters)
{
Statement.
parameters[
id]
=
pParameters[
id];
}
}
Statement.
execute
(
);
return
Statement.
getResult
(
);
}
catch
(
error:
SQLError)
{
this
.
logError
(
"Requête : <"
+
pQuery +
">\n"
+
error.
toString
(
));
}
finally
{
Connection.
close
(
);
}
return
null
;
}
La requête à exécuter est la suivante, les paramètres sont préfixés par le caractère @ :
INSERT
INTO
Contact (
Pseudo, Mail, DateNaissance, Avatar)
VALUES
(
@Pseudo, @Mail, @DateNaissance, @Avatar)
La méthode insertContact lance l'exécution en spécifiant la requête ainsi que les paramètres puis récupère le nouvel identifiant du contact :
public
function
insertContact
(
pContact:
Contact):
Boolean
{
var
sQuery:
String
=
"INSERT INTO Contact (Pseudo, Mail, DateNaissance, Avatar) VALUES (@Pseudo, @Mail, @DateNaissance, @Avatar)"
;
var
params:
Object
=
{
"@Pseudo"
:
pContact.
Pseudo,
"@Mail"
:
pContact.
Mail,
"@DateNaissance"
:
pContact.
DateNaissance,
"@Avatar"
:
pContact.
Avatar};
var
result:
SQLResult =
this
.
executeQuery
(
SQLMode.
UPDATE,
sQuery,
params);
if
(
result !=
null
)
{
pContact.
ID =
result.
lastInsertRowID;
return
true
;
}
return
false
;
}
Retournons dans la fenêtre de création pour appeler la méthode insertContact :
var
co:
Contact =
new
Contact
(
);
co.
Pseudo =
inputPseudo.
text
;
co.
Mail =
inputMail.
text
;
co.
DateNaissance =
inputDate.
selectedDate;
co.
Avatar =
imageToByteArray
(
inputAvatar);
if
(
Database.
Instance.
insertContact
(
co))
{
Close
(
);
}
else
{
Alert.
show
(
"Erreur lors de l'enregistrement\n\n"
+
Database.
Instance.
LastErrorMessage,
"Tutoriel SQLite developpez"
);
}
Lancez l'application, l'interface est fonctionnelle. Vous pouvez maintenant ajouter des contacts dans la base.
Étape 7 : afficher la liste des contacts▲
Premièrement, nous allons récupérer la liste des contacts de la base, puis nous verrons le moyen de l'afficher.
Nous allons utiliser notre méthode executeQuery mais il faut y ajouter la gestion du type des résultats en ajoutant un nouveau paramètre pClass.
private
function
executeQuery
(
pMode:
String
,
pQuery:
String
,
pParameters:
Object
=
null
,
pClass:
Class=
null
):
SQLResult
{
try
{
var
Connection:
SQLConnection =
new
SQLConnection
(
);
var
Statement:
SQLStatement =
new
SQLStatement
(
);
Connection.
open
(
databaseFile,
pMode);
Statement.
sqlConnection =
Connection;
Statement.
text
=
pQuery;
if
(
pParameters !=
null
)
{
for
(
var
id:
String
in
pParameters)
{
Statement.
parameters[
id]
=
pParameters[
id];
}
}
if
(
pClass !=
null
)
{
Statement.
itemClass =
pClass;
}
Statement.
execute
(
);
return
Statement.
getResult
(
);
}
catch
(
error:
SQLError)
{
this
.
logError
(
"Requête : <"
+
pQuery +
">\n"
+
error.
toString
(
));
}
finally
{
Connection.
close
(
);
}
return
null
;
}
La méthode de récupération des contacts est ensuite très simple :
public
function
getContactList
(
):
Array
{
var
result:
SQLResult =
this
.
executeQuery
(
SQLMode.
READ,
"SELECT * FROM Contact"
,
null
,
Contact);
if
(
result !=
null
)
{
return
result.
data
==
null
?
new
Array
(
) :
result.
data
;
}
return
null
;
}
Nous allons initialiser une liste de contacts dans l'interface principale et lancer le chargement lors de l'initialisation :
private
var
contactList:
ArrayCollection;
private
function
initApplication
(
):
void
{
if
(!
Database.
Instance.
createDatabase
(
))
{
Alert.
show
(
"Erreur lors de la création de la base :\n"
+
Database.
Instance.
LastErrorMessage,
"Tutoriel SQLite developpez"
);
}
else
{
var
ReturnedList:
Array
=
Database.
Instance.
getContactList
(
);
if
(
ReturnedList ==
null
)
{
Alert.
show
(
"Erreur lors de la récupération des contacts de la base :\n"
+
Database.
Instance.
LastErrorMessage,
"Tutoriel SQLite developpez"
);
}
else
{
contactList =
new
ArrayCollection
(
ReturnedList);
}
}
}
La variable contactList contient donc les contacts de la base. Les éléments de cette liste seront affichés à l'aide d'un composant TileList.
Deux propriétés principales sont à définir pour utiliser le composant :
- la première est la propriété dataProvider, elle permet de définir la source de données du composant, ici la liste des contacts ;
- la seconde itemRenderer définit le composant utilisé pour afficher un élément de la liste.
Nous allons ensuite créer ce composant.
L'objet TileList ajouté dans l'interface principale :
<
mx
:
TileList
dataProvider
=
"{ContactList}"
itemRenderer
=
"GUI.ContactRenderer"
width
=
"440"
height
=
"100%"
maxColumns
=
"1"
columnWidth
=
"420"
/>
Nous allons créer le composant ContactRenderer qui définit le rendu d'un contact de la liste à partir d'un simple composant HBox.
Dans le composant, l'objet data est utilisé pour accéder à l'élément courant. Il faut ensuite associer les propriétés de cet objet aux éléments de l'interface. Par exemple, pour afficher l'avatar du contact on associe data.Avatar à la source d'un objet Image :
<
mx
:
Image
source
=
"{data.Avatar}"
/>
De même pour la description du contact :
<
mx
:
Label
text
=
"{data.Pseudo + ' (' + data.Age + ' ans)'}"
/>
Cette technique est appelée Binding et permet de mettre à jour l'interface automatiquement lorsque la propriété est modifiée. Pour pouvoir lier les attributs de la classe Contact il faut les définir comme étant Bindable avec la syntaxe suivante :
[
Bindable]
public
function
get
Pseudo
(
):
String
{
return
pseudo;
}
Dans la classe Contact une propriété (getter) Age a été ajoutée pour afficher l'âge à partir de la date de naissance. Avec une propriété en lecture seule le changement de valeur ne peut être détecté pour le Binding avec l'interface. Il faut donc spécifier un événement particulier (ici "agePropertyChanged") :
[
Bindable
(
event=
'agePropertyChanged'
)]
public
function
get
Age
(
):
uint
{
var
dtNow:
Date
=
new
Date
(
);
var
currentMonth:
Number
=
dtNow.
getMonth
(
);
var
currentDay:
Number
=
dtNow.
getDay
(
);
var
currentYear:
Number
=
dtNow.
getFullYear
(
);
var
bdMonth:
Number
=
DateNaissance.
getMonth
(
);
var
bdDay:
Number
=
DateNaissance.
getDay
(
);
var
bdYear:
Number
=
DateNaissance.
getFullYear
(
);
var
years:
uint =
dtNow.
getFullYear
(
) -
DateNaissance.
getFullYear
(
);
if
(
currentMonth <
bdMonth ||
(
currentMonth ==
bdMonth &&
currentDay <
bdDay))
{
years--;
}
return
years;
}
Il est nécessaire de déclencher cet évènement lorsque la date de naissance est modifiée :
public
function
set DateNaissance
(
pDate:
Date
):
void
{
dateNaissance =
pDate;
dispatchEvent
(
new
Event
(
'agePropertyChanged'
));
}
Le code complet du composant ContactRenderer est le suivant :
<?xml version="1.0" encoding="utf-8"?>
<
mx
:
HBox
xmlns
:
mx
=
"http://www.adobe.com/2006/mxml"
height
=
"160"
width
=
"100%"
paddingTop
=
"5"
paddingBottom
=
"5"
paddingLeft
=
"5"
paddingRight
=
"5"
>
<
mx
:
HBox
height
=
"150"
width
=
"100%"
borderStyle
=
"solid"
borderThickness
=
"2"
paddingTop
=
"5"
paddingBottom
=
"5"
paddingLeft
=
"5"
paddingRight
=
"5"
>
<
mx
:
Image
source
=
"{data.Avatar}"
scaleContent
=
"true"
width
=
"170"
height
=
"130"
/>
<
mx
:
VBox
height
=
"100%"
verticalAlign
=
"middle"
paddingLeft
=
"10"
width
=
"100%"
horizontalScrollPolicy
=
"auto"
>
<
mx
:
Label
text
=
"{data.Pseudo + ' (' + data.Age + ' ans)'}"
/>
<
mx
:
Label
text
=
"{data.Mail}"
/>
</
mx
:
VBox>
<
mx
:
Spacer
width
=
"20"
/>
</
mx
:
HBox>
</
mx
:
HBox>
Vous pouvez lancer l'application et visualiser les contacts créés auparavant :
L'application permet maintenant de créer et visualiser les contacts, par contre lorsqu'un contact est ajouté, la liste ne se met pas à jour. Pour remédier à ce problème il faut que la classe Database émette un évènement lorsqu'un contact est ajouté. Ainsi l'interface pourra mettre à jour la liste lorsque l'évènement est détecté.
Définition du nom de l'évènement dans la classe Database :
public
static
const
DATABASE_CHANGE_EVENT_NAME:
String
=
"DATABASE_CHANGE_EVENT_NAME"
;
Émission de l'évènement dans la méthode insertContact :
dispatchEvent
(
new
Event
(
DATABASE_CHANGE_EVENT_NAME));
Pour disposer de la méthode dispatchEvent, la classe doit hériter de EventDispatcher. C'est la classe de base Logger qui va en hériter.
L'interface principale écoute l'évènement et lance la mise à jour de la liste lorsqu'il est détecté :
Database.
Instance.
addEventListener
(
Database.
DATABASE_CHANGE_EVENT_NAME,
function
(
event:
Event):
void
{
updateList
(
);
}
);
Étape 8 : modification d'un contact▲
Pour implémenter l'édition d'un contact dans notre application nous allons utiliser la fenêtre existante. Il faut simplement ajouter une méthode pour initialiser l'interface avec un contact particulier et ensuite modifier le comportement lors de la validation de la fenêtre pour ajouter ou modifier le contact suivant le cas d'utilisation. Une propriété editedContact est ajoutée pour mémoriser le contact édité.
private
var
editedContact:
Contact;
La fonction d'initialisation lors de l'édition :
public
function
initEdition
(
pContact:
Contact):
void
{
editedContact =
pContact;
inputPseudo.
text
=
pContact.
Pseudo;
inputMail.
text
=
pContact.
Mail;
inputDate.
selectedDate =
pContact.
DateNaissance;
inputAvatar.
source =
pContact.
Avatar;
}
Validation de la fenêtre :
// Formulaire valide
var
Edition:
Boolean
=
editedContact !=
null
;
if
(
editedContact ==
null
)
{
editedContact =
new
Contact
(
);
}
editedContact.
Pseudo =
inputPseudo.
text
;
editedContact.
Mail =
inputMail.
text
;
editedContact.
DateNaissance =
inputDate.
selectedDate;
editedContact.
Avatar =
ImageToByteArray
(
inputAvatar);
var
result:
Boolean
=
false
;
if
(!
Edition)
{
result =
Database.
Instance.
InsertContact
(
editedContact);
}
else
{
result =
Database.
Instance.
UpdateContact
(
editedContact);
}
if
(
result)
{
Close
(
);
}
else
{
Alert.
show
(
"Erreur lors de l'enregistrement\n\n"
+
Database.
Instance.
LastErrorMessage,
"Tutoriel SQLite developpez"
);
}
La méthode updateContact de la classe Database est créée à partir de la méthode insertContact :
public
function
updateContact
(
pContact:
Contact):
Boolean
{
var
sQuery:
String
=
"UPDATE Contact SET Pseudo=@Pseudo, Mail=@Mail, DateNaissance=@DateNaissance, Avatar=@Avatar WHERE ID=@ID"
;
var
params:
Object
=
{
"@Pseudo"
:
pContact.
Pseudo,
"@Mail"
:
pContact.
Mail,
"@DateNaissance"
:
pContact.
DateNaissance,
"@Avatar"
:
pContact.
Avatar,
"@ID"
:
pContact.
ID};
var
result:
SQLResult =
this
.
executeQuery
(
SQLMode.
UPDATE,
sQuery,
params);
return
result !=
null
;
}
Dans le composant de rendu du contact dans la liste nous ajoutons un bouton "Éditer" qui va ouvrir la fenêtre d'édition du contact :
<
mx
:
LinkButton
label
=
"Editer"
click
=
"editContact()"
/>
Avec la fonction editContact qui initialise la fenêtre :
private
function
editContact
(
):
void
{
var
window:
IFlexDisplayObject =
PopUpManager.
createPopUp
(
this
.
parent.
parent.
parent,
ContactWindow ,
true
);
var
EditionWindow:
ContactWindow =
ContactWindow
(
window);
EditionWindow.
initEdition
(
data
as Contact);
PopUpManager.
centerPopUp
(
EditionWindow);
}
Grâce au Binding des éléments de la liste, une fois le contact édité, la mise à jour de l'interface est automatique.
Étape 9 : suppression d'un contact▲
Pour la suppression d'un contact un autre bouton "Supprimer" est ajouté :
<
mx
:
LinkButton
label
=
"Supprimer"
click
=
"delete()"
/>
Avant de supprimer le contact, une confirmation de la part de l'utilisateur n'est jamais de trop.
La méthode confirm ci-dessous permet de l'effectuer plus simplement :
private
function
confirm
(
pMessage:
String
,
pThisObject:
Object
,
pHandler:
Function
):
void
{
Alert.
show
(
pMessage,
"Confirmation"
,
Alert.
OK|
Alert.
CANCEL,
null
,
function
(
evt:
CloseEvent):
void
{
pHandler.
apply
(
pThisObject,
[
evt.
detail ==
Alert.
OK]
);
},
null
,
Alert.
OK);
}
Elle est utilisée de la façon suivante avant d'appeler la méthode de la classe Database :
private
function
delete
(
):
void
{
confirm
(
"Supprimer le contact ?"
,
this
,
deleteContact);
}
private
function
deleteContact
(
pResult:
Boolean
):
void
{
if
(
pResult)
{
Database.
Instance.
deleteContact
(
data
as Contact);
}
}
La méthode deleteContact de la classe Database est presque similaire à la méthode de mise à jour :
public
function
deleteContact
(
pContact:
Contact):
Boolean
{
var
sQuery:
String
=
"DELETE FROM Contact WHERE ID=@ID"
;
var
params:
Object
=
{
"@ID"
:
pContact.
ID};
var
result:
SQLResult =
this
.
executeQuery
(
SQLMode.
UPDATE,
sQuery,
params);
dispatchEvent
(
new
Event
(
DATABASE_CHANGE_EVENT_NAME));
return
result !=
null
;
}
Étape 10 : créer une pagination pour la liste des contacts▲
Nous allons effectuer une pagination sur la liste de contacts, ce qui revient à limiter le nombre d'éléments de la liste et à placer des boutons pour parcourir la liste.
La pagination reste indépendante du composant utilisé pour afficher la liste de contact.
Quatre boutons sont nécessaires : page suivante, page précédente, première page et dernière page.
Un label doit indiquer les contacts affichés ainsi que le nombre total de contacts.
L'interface est donc la suivante (le bouton "Nouveau contact" a été déplacé) :
Pour implémenter cette pagination nous avons besoin de connaître le nombre total d'éléments.
Dans la classe Database la méthode getContactCount récupère cette valeur qui est stockée dans une variable contactCount :
public
function
getContactCount
(
):
uint
{
var
result:
SQLResult =
this
.
executeQuery
(
SQLMode.
READ,
"SELECT COUNT(ID) as RecordCount FROM Contact"
);
if
(
result !=
null
&&
result.
data
!=
null
)
{
return
result.
data
[
0
].
RecordCount;
}
return
null
;
}
Une variable pageItemCount indique le nombre d'éléments dans une page.
Ce nombre est défini à deux dans l'exemple mais peut donc être changé simplement si besoin.
Pour pouvoir griser les boutons suivant les éléments affichés nous utilisons deux autres variables qui indiquent si il y a des éléments précédents ou suivants.
Deux dernières variables mémorisent les index du premier et du dernier élément de la liste.
Elles sont utilisées pour pouvoir récupérer les contacts correspondants dans la base de données.
Pour récupérer seulement une partie des contacts, il faut modifier la fonction getContactList de la classe Database, en y ajoutant deux paramètres pour le nombre d'éléments à sélectionner ainsi que l'index du premier élément :
public
function
getContactList
(
pStartIndex:
uint,
pCount:
uint):
Array
{
var
sQuery:
String
=
"SELECT * FROM Contact LIMIT @count OFFSET @start"
;
var
params:
Object
=
{
"@start"
:
pStartIndex,
"@count"
:
pCount};
var
result:
SQLResult =
this
.
executeQuery
(
SQLMode.
READ,
sQuery,
params,
Contact);
if
(
result !=
null
)
{
return
result.
data
==
null
?
new
Array
(
) :
result.
data
;
}
return
null
;
}
Dans l'interface principale les quatre méthodes de navigation sont les suivantes :
private
function
firstPage
(
):
void
{
firstItemIndex =
0
;
updateList
(
);
}
private
function
nextPage
(
):
void
{
firstItemIndex +=
pageItemCount;
updateList
(
);
}
private
function
previousPage
(
):
void
{
firstItemIndex -=
pageItemCount;
updateList
(
);
}
private
function
lastPage
(
):
void
{
firstItemIndex =
contactCount -
contactCount %
pageItemCount;
updateList
(
);
}
Pour naviguer dans la liste, les méthodes mettent à jour l'index du premier élément à afficher, puis lancer la méthode de mise jour de la liste.
Lorsqu'un contact est inséré, supprimé ou encore lors de l'initialisation cette mise à jour doit aussi récupérer le nombre de contacts de la base.
La méthode updateList modifiée est la suivante :
private
function
updateList
(
pUpdateCount:
Boolean
=
false
):
void
{
if
(
pUpdateCount)
{
contactCount =
Database.
Instance.
getContactCount
(
);
firstItemIndex =
0
;
}
var
ReturnedList:
Array
=
Database.
Instance.
getContactList
(
firstItemIndex,
pageItemCount);
if
(
ReturnedList ==
null
)
{
Alert.
show
(
"Erreur lors de la récupération des contacts de la base :\n"
+
Database.
Instance.
LastErrorMessage,
"Tutoriel SQLite developpez"
);
}
else
{
contactList =
new
ArrayCollection
(
ReturnedList);
lastItemIndex =
firstItemIndex +
contactList.
length
;
hasNextItemToDisplay =
lastItemIndex <
contactCount;
hasPreviousItemToDisplay =
firstItemIndex >
0
;
}
}
La pagination est terminée, les boutons peuvent être utilisés pour afficher les différents contacts de la base :
Conclusion▲
Les sources de l'application sont disponibles ICI.
L'application à installer est disponible ICI.
(clic droit pour afficher le code source à partir de l'application)
Liens utiles:
Consultez aussi l'article sur l'utilisation de SQLite dans AIR.
Un grand merci à ellene pour son aide et à jacques_jean et Kerod pour leur relecture.