Cours création d’application mobile avec Android et base de données SQLITE
Cours création d’application mobile avec Android et base de données SQLITE
...
I - SQLite et Android
I-A - Qu'est-ce que SQLite?
SQLite est une base de données open source, qui supporte les fonctionnalités standards des bases de données relationnelles comme la syntaxe SQL, les transactions et les prepared statement. La base de données nécessite peu de mémoire lors de l'exécution (env. 250 ko), ce qui en fait un bon candidat pour être intégré dans d'autres environnements d'exécution. SQLite prend en charge les types de données TEXT (similaire à String en Java), INTEGER (similaire à long en Java) et REAL (similaire à double en Java). Tous les autres types doivent être convertis en l'un de ces types avant d'être enregistrés dans la base de données. SQLite ne vérifie pas si les types des données insérées dans les colonnes correspondent au type défini, par exemple, vous pouvez écrire un nombre entier dans une colonne de type chaîne de caractères et vice versa.
I-B - SQLite sous Android
SQLite est intégrée dans chaque appareil Android. L'utilisation d'une base de données SQLite sous Android ne nécessite pas de configuration ou d'administration de la base de données. Vous devez uniquement définir les instructions SQL pour créer et mettre à jour la base de données. Ensuite, celleci est gérée automatiquement pour vous, par la plate-forme Android. L'accès à une base de données SQLite implique l'accès au système de fichiers. Cela peut être lent. Par conséquent, il est recommandé d'effectuer les opérations de base de données de manière asynchrone. Si votre application crée une base de données, celle-ci est par défaut enregistrée dans le répertoire DATA /data/ APP_NAME/databases/FILENAME. Ce chemin de fichier est obtenu sur la base des règles suivantes : DATA est le chemin retourné par la méthode Environment.getDataDirectory(), APP_NAME est le nom de votre application. FILENAME est le nom de la base de données que vous renseignez dans le code de votre application.
III - Architecture de SQLite
III-A - Paquetages
Le paquetage android.database contient toutes les classes nécessaires pour travailler avec des bases de données.
Le paquetage android.database.sqlite contient les classes spécifiques à SQLite.
III-B - Création et mise à jour de la base de données avec SQLiteOpenHelper
Pour créer et mettre à jour une base de données dans votre application Android, vous créez une classe qui hérite de SQLiteOpenHelper. Dans le constructeur de votre sous-classe, vous appelez la méthode super() de SQLiteOpenHelper, en précisant le nom de la base de données et sa version actuelle. Dans cette classe, vous devez redéfinir les méthodes suivantes pour créer et mettre à jour votre base de données.
- onCreate() - est appelée par le framework pour accéder à une base de données qui n'est pas encore créée.
- onUpgrade() - est appelée si la version de la base de données est augmentée dans le code de votre application.
Cette méthode vous permet de mettre à jour un schéma de base de données existant ou de supprimer la base de données existante et la recréer par la méthode onCreate().
Les deux méthodes reçoivent en paramètre un objet SQLiteDatabase qui est la représentation Java de la base de données. La classe SQLiteOpenHelper fournit les méthodes getReadableDatabase() et getWriteableDatabase() pour accéder à un objet SQLiteDatabase en lecture, respectif en écriture. Les tables de base de données doivent utiliser l'identifiant _id comme clé primaire de la table. Plusieurs fonctions Android s'appuient sur cette norme.
C'est une bonne pratique de créer une classe par table. Cette classe définit des méthodes statiques onCreate() et onUpgrade(), qui sont appelées dans les méthodes correspondantes de la superclasse SQLiteOpenHelper. De cette façon, votre implémentation de SQLiteOpenHelper reste lisible, même si vous avez plusieurs tables
III-C - La classe SQLiteDatabase
SQLiteDatabase est la classe de base pour travailler avec une base de données SQLite sous Android et fournit des méthodes pour ouvrir, effectuer des requêtes, mettre à jour et fermer la base de données. Plus précisément, SQLiteDatabase fournit les méthodes insert(), update() et delete(). En outre, elle fournit la méthode execSQL(), qui permet d'exécuter une instruction SQL directement. L'objet ContentValues permet de définir des clés/valeurs. La clé représente l'identifiant de la colonne de la table et la valeur représente le contenu de l'enregistrement dans cette colonne. ContentValues peut être utilisé pour insertions et mises à jour des enregistrements de la base de données. Des requêtes peuvent être créées via les méthodes rawQuery() et query(), ou par la classe SQLiteQueryBuilder. rawQuery() accepte directement une requête SQL SELECT en entrée. query() fournit une interface structurée pour spécifier la requête SQL. SQLiteQueryBuilder est une (conivience class) classe de confort permettant de simplifier la construction des requêtes SQL.
III-D - Exemple rawQuery()
Le code suivant montre un exemple d'appel de la méthode rawQuery().
rawQuery()
Cursor cursor = getReadableDatabase().
rawQuery("select * from todo where _id = ?", new String[] { id });
III-E - Exemple query()
Le code suivant montre un exemple d'appel de la méthode query().
query()
return database.query(DATABASE_TABLE,
new String[] { KEY_ROWID, KEY_CATEGORY, KEY_SUMMARY,
KEY_DESCRIPTION }, null, null, null, null, null);
La méthode query() accepte les paramètres suivants :
Tableau 1 Paramètres de la méthode query()
…
Si une condition n'est pas requise, vous pouvez transmettre la valeur null, par exemple, pour une clause group by. La "whereClause" est spécifiée sans le mot clé "where", par exemple, une clause "where" pourrait ressembler à : " _id = 19 et résumé =" . Si vous spécifiez des valeurs génériques dans la clause where par des ?, vous les passez comme paramètres de requête de type selectionArgs.
III-F - Cursor
Une requête retourne un objet Cursor. Un curseur représente le résultat d'une requête et pointe généralement vers une ligne de ce résultat. De cette façon, Android peut gérer les résultats de la requête de manière efficace, car il n'a pas à charger toutes les données en mémoire. Utilisez la méthode getCount() pour obtenir le nombre d'éléments résultant de la requête. Pour vous déplacer entre les lignes individuelles de données, vous pouvez utiliser les méthodes MoveToFirst() et MoveToNext(). La méthode isAfterLast() permet de vérifier si la fin du résultat de la requête a été atteinte. (La classe) Cursor fournit des méthodes get*() par type de données : getLong(columnIndex), getString(columnIndex), pour accéder aux données d'une colonne de la position courante du résultat. Le "columnIndex" est l'index de la colonne à laquelle vous accédez. Cursor fournit également la méthode getColumnIndexOrThrow(String) qui permet d'obtenir l'index d'une colonne à partir de son nom passé en paramètre. Un curseur doit être fermé par l'appel à la méthode close().
III-G - ListViews, ListActivities et SimpleCursorAdapter
ListViews sont des vues qui permettent d'afficher une liste d'éléments. ListActivities sont des activités spécialisées qui rendent plus facile l'usage des ListViews. Pour travailler avec des bases de données et ListViews vous pouvez utiliser un SimpleCursorAdapter, qui permet de définir une mise en page pour chaque ligne des ListViews. Vous définissez également un tableau qui contient les noms de colonnes et un autre tableau contenant les identifiants des Vues dans lesquelles les données seront affichées. La classe SimpleCursorAdapter effectuera le mappage des colonnes vers les vues, basé sur le curseur qui lui est passé . Pour obtenir le curseur, vous devez utiliser la classe Loader.
IV - Tutoriel : utiliser SQLite
IV-A - Présentation du projet
La partie suivante démontre comment travailler avec une base de données SQLite. Nous allons utiliser un objet d'accès aux données (DAO) qui gérera les données pour nous. Le DAO prend en charge la gestion de la connexion à la base de données, l'accès aux données et leur modification. Il se chargera également de la conversion des objets de la base de données en objets Java, de sorte que le code de notre interface utilisateur n'a pas à s'occuper de la couche de persistance. L'application résultante se présentera ainsi.
L'utilisation d'un DAO n'est pas toujours une bonne approche. Un DAO crée des modèles objet Java ; l'utilisation d'une base de données directement ou par l'intermédiaire d'un fournisseur de contenu est généralement plus efficace en terme des ressources car vous pouvez éviter la création des modèles objet. Je démontre encore l'utilisation du DAO dans cet exemple pour commencer avec un exemple relativement simple. Utilisez la dernière version Android 4.0, API Level 15. Sinon, vous devrez faire usage de la classe Loader, utilisée à partir de Android 3.0 pour la gestion d'un curseur de base de données, et cette classe ajoute une complexité supplémentaire.
IV-B - Créer le projet
Créez le nouveau projet Android, nommez-le de.vogella.android.sqlite.first, puis une activité nommée TestDatabaseActivity.
IV-C - La base de données et le modèle de données
Créez la classe MySQLiteHelper. Cette classe se charge de la création de la base de données. La méthode onUpgrade() va simplement supprimer toutes les données existantes et recréer la table. Elle définit également plusieurs constantes pour le nom et les colonnes de la table.
classeMySQLiteHelper
package de.vogella.android.sqlite.first;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class MySQLiteHelper extends SQLiteOpenHelper {
public static final String TABLE_COMMENTS = "comments";
public static final String COLUMN_ID = "_id";
public static final String COLUMN_COMMENT = "comment";
private static final String DATABASE_NAME = "commments.db";
private static final int DATABASE_VERSION = 1;
// Commande sql pour la création de la base de données
private static final String DATABASE_CREATE = "create table "
+ TABLE_COMMENTS + "(" + COLUMN_ID
+ " integer primary key autoincrement, " + COLUMN_COMMENT
+ " text not null);";
public MySQLiteHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase database) {
database.execSQL(DATABASE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(MySQLiteHelper.class.getName(),
"Upgrading database from version " + oldVersion + " to "
+ newVersion + ", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS " + TABLE_COMMENTS);
onCreate(db);
}
}
Créez la classe Comment. Cette classe est notre modèle et contient les données que nous allons enregistrer dans la base de données, et afficher dans l'interface utilisateur.
classeComment
package de.vogella.android.sqlite.first;
public class Comment {
private long id;
private String comment;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
// Sera utilisée par ArrayAdapter dans la ListView
@Override
classeComment
public String toString() {
return comment;
}
}
Créez la classe CommentsDataSource. Cette classe est notre DAO. Elle maintient la connexion avec la base de données et prend en charge l'ajout des nouveaux commentaires et le retour de tous les commentaires.
classCommentsDataSource
package de.vogella.android.sqlite.first;
import java.util.ArrayList;
import java.util.List;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
public class CommentsDataSource {
// Champs de la base de données
private SQLiteDatabase database;
private MySQLiteHelper dbHelper;
private String[] allColumns = { MySQLiteHelper.COLUMN_ID,
MySQLiteHelper.COLUMN_COMMENT };
public CommentsDataSource(Context context) {
dbHelper = new MySQLiteHelper(context);
}
public void open() throws SQLException {
database = dbHelper.getWritableDatabase();
}
public void close() {
dbHelper.close();
}
public Comment createComment(String comment) {
ContentValues values = new ContentValues();
values.put(MySQLiteHelper.COLUMN_COMMENT, comment);
long insertId = database.insert(MySQLiteHelper.TABLE_COMMENTS, null,
values);
Cursor cursor = database.query(MySQLiteHelper.TABLE_COMMENTS,
allColumns, MySQLiteHelper.COLUMN_ID + " = " + insertId, null,
null, null, null);
cursor.moveToFirst();
Comment newComment = cursorToComment(cursor);
cursor.close();
return newComment;
}
public void deleteComment(Comment comment) {
long id = comment.getId();
System.out.println("Comment deleted with id: " + id);
database.delete(MySQLiteHelper.TABLE_COMMENTS, MySQLiteHelper.COLUMN_ID
+ " = " + id, null);
}
public List<Comment> getAllComments() {
List<Comment> comments = new ArrayList<Comment>();
Cursor cursor = database.query(MySQLiteHelper.TABLE_COMMENTS,
allColumns, null, null, null, null, null);
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
classCommentsDataSource
Comment comment = cursorToComment(cursor);
comments.add(comment);
cursor.moveToNext();
}
// assurez-vous de la fermeture du curseur
cursor.close();
return comments;
}
private Comment cursorToComment(Cursor cursor) {
Comment comment = new Comment();
comment.setId(cursor.getLong(0));
comment.setComment(cursor.getString(1));
return comment;
}
}
IV-D - L'interface utilisateur
Modifiez votre fichier main.xml dans le dossier res/layout comme dans le code suivant. Cette mise en page a deux boutons pour ajouter et supprimer des commentaires et une ListView qui sera utilisée pour afficher les commentaires existants. Le texte du commentaire sera généré plus tard dans l'activité par un petit générateur aléatoire.
mainXML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:id="@+id/group"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<Button
android:id="@+id/add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add New"
android:onClick="onClick"/>
<Button
android:id="@+id/delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Delete First"
android:onClick="onClick"/>
</LinearLayout>
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />
</LinearLayout>
Modifiez votre classe TestDatabaseActivity comme ci-dessous. Nous utilisons ici une ListActivity pour afficher les données.
classeTestDatabaseActivity
package de.vogella.android.sqlite.first;
import java.util.List;
classeTestDatabaseActivity
import java.util.Random;
import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
public class TestDatabaseActivity extends ListActivity {
private CommentsDataSource datasource;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
datasource = new CommentsDataSource(this);
datasource.open();
List<Comment> values = datasource.getAllComments();
// utilisez SimpleCursorAdapter pour afficher les
// éléments dans une ListView
ArrayAdapter<Comment> adapter = new ArrayAdapter<Comment>(this,
android.R.layout.simple_list_item_1, values);
setListAdapter(adapter);
}
// Sera appelée par l'attribut onClick
// des boutons déclarés dans main.xml
public void onClick(View view) {
@SuppressWarnings("unchecked")
ArrayAdapter<Comment> adapter = (ArrayAdapter<Comment>) getListAdapter();
Comment comment = null;
switch (view.getId()) {
case R.id.add:
String[] comments = new String[] { "Cool", "Very nice", "Hate it" };
int nextInt = new Random().nextInt(3);
// enregistrer le nouveau commentaire dans la base de données
comment = datasource.createComment(comments[nextInt]);
adapter.add(comment);
break;
case R.id.delete:
if (getListAdapter().getCount() > 0) {
comment = (Comment) getListAdapter().getItem(0);
datasource.deleteComment(comment);
adapter.remove(comment);
}
break;
}
adapter.notifyDataSetChanged();
}
@Override
protected void onResume() {
datasource.open();
super.onResume();
}
@Override
protected void onPause() {
datasource.close();
super.onPause();
}
}