You are on page 1of 41

8

Bases de donnes
et fournisseurs de contenu
Au sommaire de ce chapitre:

Crer des bases de donnes et utiliser SQLite

Utiliser les fournisseurs de contenu, les curseurs et les content values pour
stocker, partager et consommer des donnes dapplication

Interroger des fournisseurs de contenu de faon asynchrone grce aux chargeurs


de curseurs

Ajouter des fonctionnalits de recherche vos applications

Utiliser les fournisseurs de contenu natifs MediaStore, Contacts et Agenda

Ce chapitre prsente les mcanismes de stockage persistant dAndroid, en commenant


par la base de donnes SQLite. Cette API offre une bibliothque de bases de donnes
SQL puissante, qui fournit une couche de persistance robuste et entirement contrlable.
Vous apprendrez galement construire et utiliser des fournisseurs de contenu pour
stocker, partager et consommer des donnes structures dans et entre vos applications.
Les fournisseurs de contenu offrent une interface standard vers nimporte quelle
source de donnes en dcouplant la couche de stockage des donnes de la couche
applicative. Vous verrez comment interroger les fournisseurs de contenu de faon
asynchrone afin de garantir la ractivit de votre application.
Bien que laccs une base de donnes soit rserv lapplication qui la cre, les
fournisseurs de contenu offrent vos applications un mcanisme standard pour
partager leurs donnes et consommer les donnes dautres applications notamment
celles de nombreux dpts de donnes natifs.
Vous apprendrez galement ajouter une fonction de recherche vos applications
et construire des fournisseurs de contenu capables de fournir des suggestions de
recherche en temps rel.

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 263

03/08/12 07:26

264

Android 4

Les fournisseurs de contenu pouvant tre utiliss entre les applications, vous avez
la possibilit dintgrer plusieurs fournisseurs natifs dans vos propres applications,
comme les contacts, lagenda et le MediaStore. Vous apprendrez stocker et
rcuprer des donnes de ces applications essentielles dAndroid afin de fournir
vos utilisateurs un plus grand confort et une intgration totale avec le systme.

Introduction aux bases de donnes Android


Android assure la persistance des donnes structures laide dune combinaison de
bases de donnes SQLite et de fournisseurs de contenu. Les bases de donnes SQLite
peuvent servir stocker les donnes des applications au moyen dune approche structure et gre. Android offre une bibliothque SQLite complte. Chaque application
peut crer ses propres bases sur lesquelles elle dispose dun contrle absolu.
Si vous avez cr votre dpt de donnes sous-jacent, les fournisseurs de contenu
offrent une interface gnrique et bien dfinie pour lutilisation et le partage de
donnes.Bases de donnes SQLite
Vous pouvez crer laide de SQLite des bases de donnes relationnelles indpendantes
pour vos applications. Utilisez-les pour stocker et grer des donnes dapplication
complexes et structures.
Les bases de donnes Android sont stockes sur votre terminal (ou votre mulateur)
dans le dossier /data/data/<nom_package>/databases. Toutes les bases de donnes
sont prives et ne sont accessibles que par lapplication qui les a cres.
La conception de bases de donnes est un vaste sujet qui mriterait beaucoup plus de
temps que celui que nous pouvons lui accorder dans ce livre. Il faut souligner que les
bonnes pratiques de conception des bases de donnes sappliquent sous Android. En
particulier, lorsque vous crez des bases de donnes pour des appareils aux ressources
limites (comme des tlphones mobiles), il est important de normaliser vos donnes
pour rduire les redondances.
Fournisseurs de contenu
Les fournisseurs de contenu mettent disposition une interface pour la publication et
la consommation des donnes, reposant sur un modle simple dadressage par URI
utilisant le schma content://. Ils vous permettent de dcoupler la couche applicative
de la couche de donnes, rendant ainsi vos applications indpendantes des sources
des donnes en masquant les sources de donnes sous-jacentes.
Les fournisseurs de contenu peuvent tre partags entre les applications et interrogs;
leurs enregistrements peuvent tre mis jour ou supprims et de nouvelles donnes
peuvent y tre ajoutes. Toute application possdant les permissions appropries peut
ajouter, supprimer ou mettre jour des donnes dune autre application, y compris
celles des fournisseurs de contenu natifs dAndroid.

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 264

03/08/12 07:26

Chapitre 8

Bases de donnes et fournisseurs de contenu

265

Plusieurs fournisseurs de contenu natifs sont accessibles par les applications tierces,
notamment le gestionnaire des contacts, la base de mdias et lagenda, comme nous
le verrons plus loin dans ce chapitre.
En publiant vos propres fournisseurs de contenu, vous vous donnez la possibilit
(ainsi qu dautres dveloppeurs) dincorporer et dtendre vos donnes dans de
nouvelles applications.

Introduction SQLite
SQLite est un systme de gestion de bases de donnes relationnelles (SGBDR) bien
connu. Il est:

open-source;

conforme aux standards;

lger;

mono tiers.

Il a t implment sous la forme dune bibliothque C compacte incluse dans Android.


tant implment sous forme de bibliothque et non excut dans un processus
distinct, chaque base de donnes SQLite fait partie intgrante de lapplication qui
la cre. Cela rduit les dpendances externes, minimise la latence et simplifie le
verrouillage des transactions et la synchronisation.
SQLite a une rputation de grande fiabilit et il est le SGBDR choisi par de nombreux
appareils lectroniques, notamment beaucoup de lecteurs MP3 et de smartphones.
Lger et puissant, il diffre des moteurs de bases de donnes conventionnels par son
typage faible des colonnes, ce qui signifie que les valeurs dune colonne ne doivent
pas forcment tre dun seul type. Chaque valeur est type individuellement par
ligne. La consquence en est que la vrification de type nest pas obligatoire lors de
laffectation ou de lextraction des valeurs des colonnes dune ligne.
Info
Vous trouverez des informations compltes sur SQLite sur le site officiel http://www.
sqlite.org/, en particulier sur ses forces et limites.

Curseurs et Content Values


Les Content Values servent insrer de nouvelles lignes dans des tables. Chaque
objet ContentValues reprsente une ligne de la table sous la forme dune association
des noms de colonnes vers les valeurs.

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 265

03/08/12 07:26

266

Android 4

Sous Android, le rsultat des requtes est renvoy sous la forme dobjets Cursor.
Ceux-ci sont des pointeurs vers les rsultats et non des extractions des valeurs. Ils
fournissent un moyen de contrler votre position (ligne) dans le rsultat dune requte,
do leur nom..
La classe Cursor inclut de nombreuses fonctions de dplacement dont quelques
exemples suivent:
moveToFirst.
moveToNext.

Dplace le curseur sur la premire ligne du rsultat.

Dplace le curseur sur la ligne suivante.

moveToPrevious.
getCount.

Dplace le curseur sur la ligne prcdente.

Renvoie le nombre de lignes du rsultat.

getColumnIndexOrThrow. Renvoie lindice de la colonne indique et renvoie une

exception si aucune colonne de ce nom nexiste.


getColumnName.

Renvoie le nom de la colonne lindice indiqu.

getColumnNames.

Renvoie un tableau de chanes contenant les noms de toutes


les colonnes du curseur courant.

getPosition.

Renvoie la position courante du curseur.

Android fournit un mcanisme pratique pour garantir que les requtes seffectuent
de faon asynchrone: la classe CursorLoader et son gestionnaire associ ont t
introduits par Android 3.0 (API level 11) et font dsormais partie de la bibliothque
support, ce qui vous permet den tirer profit tout en supportant les versions plus
anciennes dAndroid.
Plus loin dans ce chapitre, vous apprendrez comment interroger une base de donnes
et extraire des valeurs spcifiques de ligne et de colonne des curseurs rsultants.

Utiliser des bases de donnes SQLite


Cette section explique comment crer et utiliser les bases de donnes SQLite dans
vos applications.
Lorsque lon utilise des bases de donnes, il est conseill dencapsuler la base de
donnes sous-jacente et de nexposer que les mthodes et les constantes publiques
ncessaires aux interactions avec la base en se servant dune classe utilitaire. Cette
classe doit exposer les constantes de la base de donnes notamment les noms des
colonnes ncessaires son remplissage et son interrogation. Plus loin dans ce
chapitre, nous prsenterons les fournisseurs de contenu, qui peuvent galement servir
exposer ces constantes dinteraction.
Le Listing8.1 montre un exemple de constantes de la base de donnes qui devraient
tre rendues publiques dans une classe utilitaire.
2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 266

03/08/12 07:26

Chapitre 8

Bases de donnes et fournisseurs de contenu

267

Listing8.1: Squelette de code pour les constantes de la classe utiliitaire


// Colonne index (cl) utiliser dans les clauses where.
public static final String KEY_ID="_id";
// Nom et indice de chaque colonne dans la base de donnes.
// Ces noms doivent tre vocateurs.
public static final String KEY_GOLD_HOARD_NAME_COLUMN =
GOLD_HOARD_NAME_COLUMN;
public static final String KEY_GOLD_HOARD_ACCESSIBLE_COLUMN =
OLD_HOARD_ACCESSIBLE_COLUMN;
public static final String KEY_GOLD_HOARDED_COLUMN =
GOLD_HOARDED_COLUMN;
// faire: crer des variables publiques pour chaque colonne.

Introduction SQLiteOpenHelper
est une classe abstraite utilise pour implmenter un modle de
bonnes pratiques pour la cration, louverture et la mise jour des bases de donnes.

SQLiteOpenHelper

En limplmentant, vous masquez la logique utilise pour dcider si une base de


donnes doit tre cre ou mise jour avant dtre ouverte et vous garantissez que
chaque opration sexcute de faon efficace.
Il est conseill de reporter la cration et louverture des bases de donnes tant quelles
ne sont pas ncessaires. Lobjet SQLiteOpenHelper met en cache les instances de
la base une fois quelles ont t ouvertes: vous pouvez donc demander ouvrir la
base juste avant deffectuer une requte ou une transaction. Pour la mme raison, il
nest pas ncessaire de fermer la base de donnes manuellement, sauf si vous nen
avez plus besoin.
Info
Les oprations sur les bases de donnes, notamment leur ouverture ou leur cration,
peuvent durer un certain temps. Pour tre sr que cela ne pnalisera pas le confort
dutilisation de votre application, faites en sorte que toutes les transactions sur la base
soient asynchrones.

Le Listing 8.2 montre comment tendre SQLiteOpenHelper en redfinissant le


constructeur et les mthodes onCreate et onUpgrade pour prendre en charge, respectivement, la cration dune base de donnes et la mise jour vers une nouvelle version.
Listing 8.2: Implmentation de SQLiteOpenHelper
private static class HoardDBOpenHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = myDatabase.db;
private static final String DATABASE_TABLE = GoldHoards;
private static final int DATABASE_VERSION = 1;

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 267

03/08/12 07:26

268

Android 4

// Instruction SQL pour crer une base de donnes.


private static final String DATABASE_CREATE = create table +
DATABASE_TABLE + ( + KEY_ID +
integer primary key autoincrement, +
KEY_GOLD_HOARD_NAME_COLUMN + text not null, +
KEY_GOLD_HOARDED_COLUMN + float, +
KEY_GOLD_HOARD_ACCESSIBLE_COLUMN + integer);;
public HoardDBOpenHelper(Context context, String name,
CursorFactory factory, int version) {
super(context, name, factory, version);
}
// Appele lorsque aucune base nexiste sur le disque et que la classe
// utilitaire doit en crer une nouvelle.
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE);
}
// Appele si une version de la base ne correspond pas, ce qui signifie
// que la version de la base sur le disque doit tre mise jour vers
// la version courante.
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion,
int newVersion) {
// Inscrit la mise jour de version dans le journal.
Log.w(TaskDBAdapter, Mise jour de la version +
oldVersion + vers la version +
newVersion + , ce qui dtruira toutes les anciennes donnes);
// Mise jour de la base existante pour se conformer la nouvelle
// version. Plusieurs versions antrieures peuvent tre gres en
// comparant les valeurs de oldVersion et newVersion.
// Le cas le plus simple consiste supprimer lancienne table et
// en crer une nouvelle.
db.execSQL(DROP TABLE IF IT EXISTS + DATABASE_TABLE);
// Cration dune nouvelle.
onCreate(db);
}
}

Info
Dans cet exemple, onUpgrade supprime simplement la table existante et la remplace
par sa nouvelle dfinition. Cest souvent la solution la plus simple et la plus pratique.
Cependant, pour les donnes importantes qui ne sont pas synchronises avec des services
en ligne ou qui sont difficiles reproduire, une meilleure approche consiste migrer
les donnes existantes dans la nouvelle table.

Pour accder une base de donnes via cette classe utilitaire, appelez
getWritableDatabase ou getReadableDatabase pour ouvrir et renvoyer, respectivement, une instance en criture ou en lecture de la base sous-jacente.

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 268

03/08/12 07:26

Chapitre 8

Bases de donnes et fournisseurs de contenu

269

En coulisses, si la base nexiste pas, lobjet SQLiteOpenHelper excute son gestionnaire onCreate. Si la version de la base a chang, le gestionnaire onUpgrade sera
lanc. Dans les deux cas, lappel getWritableDatabase ou getReadableDatabase
renverra la base de donnes en cache, nouvellement ouverte, nouvellement cre
ou mise jour.
Lorsquune base de donnes a t ouverte, le SQLiteOpenHelper met en cache la
base de donnes qui vient dtre ouverte: vous pouvez (et devriez) donc utiliser ces
mthodes pour chaque interrogation ou transaction sur la base, au lieu de mettre en
cache la base de donnes dans votre application.
Un appel getWritableDatabase peut chouer en raison dun problme despace
disque ou de permissions. Il est donc conseill de se replier sur la mthode
getReadableDatabase pour les requtes. Dans la plupart des cas, cette mthode
renverra la mme instance de base de donnes en cache et ouverte en criture que
getWritableDatabase, sauf si la base nexiste pas encore ou que les mmes problmes
de permission ou despace disque interviennent, auquel cas elle renverra une copie
en lecture seule.
Info
Pour crer ou mettre jour une base de donnes, celle-ci doit tre ouverte en criture.
Il est donc gnralement conseill de tenter dabord de louvrir en criture et de ne
se replier vers une ouverture en lecture seule que si cette premire tentative choue.

Ouvrir et crer des bases de donnes sans SQLiteOpenHelper


Si vous prfrez grer directement la cration, louverture et le contrle de version
de vos bases de donnes, vous pouvez utiliser la mthode openOrCreateDatabase
dans le contexte de lapplication:
SQLiteDatabase db = context.openOrCreateDatabase(DATABASE_NAME,
Context.MODE_PRIVATE,
null);

Aprs avoir cr la base de donnes, vous devez grer la cration et les mises jour
de versions qui sont normalement prises en charge dans les gestionnaires onCreate
et onUpdate de SQLiteOpenHelper gnralement en utilisant la mthode execSQL
de la base de donnes pour crer et supprimer les tables.
Il est conseill de reporter la cration et louverture des bases de donnes tant quelles
ne sont pas ncessaires et de mettre en cache les instances de la base aprs leur
ouverture afin de limiter les cots que ces oprations induisent en termes defficacit.
Au minimum, ces oprations doivent tre traites de faon asynchrone pour viter
de perturber le thread principal de lapplication.

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 269

03/08/12 07:26

270

Android 4

Considrations sur la conception dune base de donnes Android


Il existe plusieurs considrations spcifiques Android que vous devrez garder
lesprit lorsque vous concevrez votre base de donnes.
Les fichiers (comme les bitmaps ou les fichiers audio) ne sont en gnral pas
stocks dans une base. Utilisez une chane pour stocker un chemin vers le fichier,
de prfrence une URI qualifie.
Il est recommand, bien que non obligatoire, que toutes les tables contiennent
une colonne auto-incrmente qui servira dindex unique chaque ligne. Si vous
prvoyez de partager votre table en utilisant un fournisseur de contenu, cette
colonne devient obligatoire.
Interroger une base de donnes
Chaque requte sur une base de donnes est renvoye sous la forme dun objet Cursor.
Ceci permet Android de grer plus efficacement les ressources en retrouvant et en
librant les valeurs de lignes et de colonnes la demande.
Pour excuter une requte sur une base de donnes, utilisez la mthode query en lui
passant les lments suivants:

Un boolen facultatif indiquant si le rsultat ne doit contenir que des valeurs


uniques.
Le nom de la table interroger.
Une projection sous forme de tableau de chanes numrant les colonnes
inclure dans le rsultat.
Une clause where dfinissant les lignes ramener. Vous pouvez inclure des
jokers ? qui seront remplacs par les valeurs passes par le paramtre des
arguments de slection.
Un tableau darguments de slection qui remplaceront les? de la clause where.
Une clause group by qui dfinira comment les lignes de rsultat devront tre
groupes.
Une clause having dfinissant les groupes de lignes inclure lorsque lon a
indiqu une clause group by.
Une chane dcrivant lordre des lignes ramenes.
Une chane facultative dfinissant la limite du nombre de lignes ramenes.

Le Listing8.3 montre comment rcuprer une slection de lignes dune table SQLite.
Listing8.3: Interrogation dune base de donnes
// Prcise la projection des colonnes du rsultat. On renvoie lensemble
// de colonnes minimum correspondant nos besoins.
String[] result_columns = new String[] {
KEY_ID, KEY_GOLD_HOARD_ACCESSIBLE_COLUMN, KEY_GOLD_HOARDED_COLUMN };

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 270

03/08/12 07:26

Chapitre 8

Bases de donnes et fournisseurs de contenu

271

// Clause where pour limiter le nombre de lignes du rsultat.


String where = KEY_GOLD_HOARD_ACCESSIBLE_COLUMN + =1;
// remplacer par des instructions SQL valides en fonction des besoins.
String whereArgs[] = null;
String groupBy = null;
String having = null;
String order = null;
SQLiteDatabase db = hoardDBOpenHelper.getWritableDatabase();
Cursor cursor = db.query(HoardDBOpenHelper.DATABASE_TABLE,
result_columns, where,
whereArgs, groupBy, having, order);

Info
Dans le Listing 8.3, on ouvre une instance de base de donnes SQLite laide de limplmentation de SQLiteOpenHelper, qui reporte la cration et louverture des instances de
base tant quelles ne sont pas ncessaires et les met en cache aprs leur ouverture.
En consquence, il est conseill de demander une instance de base de donnes chaque
fois que lon effectue une requte ou une transaction sur la base. Pour des raisons
defficacit, vous ne devriez fermer votre instance de base de donnes que si vous
pensez que vous nen aurez plus besoin typiquement, lorsque lactivit ou le service
qui lutilise est stopp.

Extraire les rsultats dun curseur


Pour extraire les valeurs dun curseur, utilisez dabord les mthodes moveTo<location>
dcrites plus haut pour positionner le curseur sur la bonne ligne puis utilisez les
mthodes get<type> (en passant lindice de colonne) pour rcuprer la valeur dune
colonne de la ligne courante. Pour trouver lindice dune colonne particulire dans
un curseur, servez-vous de ses mthodes getColumnIndexOrThrow et getColumnIndex.
Lorsque la colonne est cense exister dans tous les cas, il est conseill dutiliser
getColumnIndexOrThrow. En revanche, appeler getColumnIndex et tester quelle renvoie
1, comme on le fait dans lextrait ci-dessous, est une technique plus efficace que
la capture des exceptions lorsque la colonne peut ne pas exister dans certains cas.
int columnIndex = cursor.getColumnIndex(KEY_COLUMN_1_NAME);
if (columnIndex > -1) {
String columnValue = cursor.getString(columnIndex);
// Traiter la valeur de la colonne.
} else {
// Faire quelque chose dautre si la colonne nexiste pas.
}

Info
Les implmentations de bases de donnes doivent publier des constantes statiques qui
donnent les noms des colonnes. Ces constantes statiques sont gnralement exposes
par la classe utilitaire ou le fournisseur de contenu.

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 271

03/08/12 07:26

272

Android 4

Le Listing8.4 montre comment parcourir un curseur et extraire une colonne de


valeurs flottantes pour en faire la moyenne.
Listing8.4 : Extraction des valeurs dun Cursor
float totalHoard = 0f;
float averageHoard = 0f;
// Trouve lindice de la (les) colonne(s) utilise(s).
int GOLD_HOARDED_COLUMN_INDEX =
cursor.getColumnIndexOrThrow(KEY_GOLD_HOARDED_COLUMN);
// Parcourt les lignes du curseur.
// Le curseur est positionn avant la premire ligne lorsquil est
// initialis : on peut donc simplement vrifier si une colonne "suivante"
// existe. Si le curseur est vide, lappel renverra false.
while (cursor.moveToNext()) {
float hoard = cursor.getFloat(GOLD_HOARDED_COLUMN_INDEX);
totalHoard += hoard;
}
// Calcule une moyenne en vrifiant les divisions par zro.
float cursorCount = cursor.getCount();
averageHoard = cursorCount > 0 ? (totalHoard / cursorCount) : Float.NaN;
// Fermeture du curseur lorsque lon nen a plus besoin.
cursor.close();

Les colonnes des bases SQLite tant faiblement types, vous pouvez les transtyper en
types valides selon vos besoins. Les valeurs stockes en virgules flottantes peuvent,
par exemple, tre lues comme des chanes.
Lorsque vous avez fini dutiliser le curseur, il est important de le fermer pour viter
les fuites mmoire et pour rduire les ressources utilises par lapplication:
cursor.close();

Ajouter, mettre jour et supprimer des lignes


La classe SQLiteDatabase expose les mthodes insert, delete et update qui encapsulent les instructions SQL requises pour ces actions. De plus, la mthode execSQL
vous permet dexcuter nimporte quelle instruction SQL valide sur vos tables si vous
souhaitez excuter ces oprations (ou dautres) manuellement.
Chaque fois que vous modifiez des valeurs de la base sous-jacente, vous devez mettre
jour vos curseurs en excutant une nouvelle requte.
Insrer de nouvelles lignes

Pour crer une nouvelle ligne, construisez un objet ContentValues et utilisez sa


mthode put pour ajouter des paires nom/valeur reprsentant chaque nom de colonne
et sa valeur associe.
2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 272

03/08/12 07:26

Chapitre 8

Bases de donnes et fournisseurs de contenu

273

Insrez la nouvelle ligne en passant cet objet la mthode insert de la base cible,
ainsi que le nom de la table (voir le Listing8.5).
Listing8.5: Insertion de nouvelles lignes dans une base
// Cre une nouvelle ligne insrer.
ContentValues newValues = new ContentValues();
// Affecte des valeurs chaque ligne.
newValues.put(KEY_GOLD_HOARD_NAME_COLUMN, hoardName);
newValues.put(KEY_GOLD_HOARDED_COLUMN, hoardValue);
newValues.put(KEY_GOLD_HOARD_ACCESSIBLE_COLUMN, hoardAccessible);
// [ ... Rpter pour chaque paire nom/valeur de colonne ... ]
// Insre la ligne.
SQLiteDatabase db = hoardDBOpenHelper.getWritableDatabase();
db.insert(HoardDBOpenHelper.DATABASE_TABLE, null, newValues);

Info
Le second paramtre pass insert dans le Listing8.5 est appel "astuce de la colonne
null".
Si vous voulez ajouter une ligne vide dans une base de donnes SQLite en passant un
objet ContentValues vide, vous devez galement passer le nom dune colonne dont la
valeur est explicitement fixe null.
Lorsque vous insrez une nouvelle ligne dans une base de donnes SQLite, vous devez
toujours prciser au moins une colonne et sa valeur correspondante qui peut tre
null. Si le second paramtre de insert est null, comme avec lastuce de la colonne null,
linsertion dun objet ContentValues vide lvera une exception.
Il est gnralement prfrable de sassurer que votre code ne tente pas dinsrer des
ContentValues vides dans une base de donnes SQLite.

Mettre jour des lignes

La mise jour se fait galement avec des valeurs de contenus.


Crez un nouvel objet ContentValues et utilisez la mthode put pour affecter de
nouvelles valeurs chaque colonne que vous voulez mettre jour. Appelez update
sur la base de donnes en lui passant le nom de la table, lobjet ContentValues modifi
et une clause where prcisant la ou les lignes mettre jour (voir le Listing8.6).
Listing8.6: Mise jour dune ligne
// Dfinit le contenu de la ligne mise jour.
ContentValues updatedValues = new ContentValues();
// Affecte une valeur pour chaque ligne.
updatedValues.put(KEY_GOLD_HOARDED_COLUMN, newHoardValue);
[ ... Rpter pour chaque colonne modifier... ]

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 273

03/08/12 07:26

274

Android 4

// Cration dune clause where dfinissant les lignes qui devront


// tre mises jour. Dfinit les paramtres ventuels de where.
String where = KEY_ID + "=" + hoardId; String whereArgs[] = null;
// Met jour la ligne indique par where avec les nouvelles valeurs.
SQLiteDatabase db = hoardDBOpenHelper.getWritableDatabase();
db.update(HoardDBOpenHelper.DATABASE_TABLE, updatedValues,
where, whereArgs);

Supprimer des lignes

Pour supprimer une ligne, appelez simplement delete sur la base en indiquant le
nom de la table et une clause where ramenant les lignes que vous voulez supprimer
(voir le Listing8.7).
Listing8.7: Suppression dune ligne
// Crer une clause where prcisant la ou les lignes supprimer.
// Dfinit les paramtres ventuels de where.
String where = KEY_GOLD_HOARDED_COLUMN + =0;
String whereArgs[] = null;
// Supprime les lignes correspondant la clause where.
SQLiteDatabase db = hoardDBOpenHelper.getWritableDatabase();
db.delete(HoardDBOpenHelper.DATABASE_TABLE, where, whereArgs);

Crer des fournisseurs de contenu


Les fournisseurs de contenu offrent une interface pour la publication de donnes qui
seront consommes laide dun rsolveur de contenus. Ils permettent de dcoupler
les sources de donnes sous-jacentes et les composants applicatifs qui consomment
les donnes, offrant ainsi un mcanisme gnrique grce auquel les applications
peuvent partager leurs donnes ou consommer les donnes fournies par dautres.
Pour crer un nouveau fournisseur de contenu, tendez la classe abstraite
ContentProvider:
public class MyContentProvider extends ContentProvider

Comme pour la classe utilitaire dcrite dans la section prcdente, il est conseill de
dfinir des constantes statiques dans cette classe notamment les noms des colonnes
et lautorit du fournisseur de contenu qui seront requises par la suite pour effectuer
des transactions sur la base de donnes ou pour linterroger.
Vous devez galement redfinir la mthode onCreate pour crer (et initialiser) la
source de donnes sous-jacente, ainsi que les mthodes query, update, delete, insert
et getType pour implmenter linterface utilise par le rsolveur de contenus pour
interagir avec les donnes, comme nous lexpliquons dans les sections qui suivent.

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 274

03/08/12 07:26

Chapitre 8

Bases de donnes et fournisseurs de contenu

275

Enregistrer les fournisseurs de contenu


Comme les activits et les services, les fournisseurs de contenu doivent tre enregistrs
dans le manifeste de votre application pour que le rsolveur puisse les dcouvrir.
Pour cela, on utilise la balise provider qui dispose dun attribut name fournissant le
nom de la classe du fournisseur et dun attribut authorities qui dfinit lURI de
base de lautorit du fournisseur.
Cette autorit du fournisseur de contenu est utilise par le rsolveur de contenus
comme une adresse et permet de retrouver la base de donnes avec laquelle on
souhaite interagir.
Chaque autorit de fournisseur devant tre unique, il est conseill dutiliser le chemin du
nom de votre paquetage pour construire cette URI. La forme gnrale de lautorit dun
fournisseur de contenu est donc: com.<NomSocit>.provider.<NomApplication>.
Une balise provider complte doit ressembler cet exemple:
<provider android:name=.MyContentProvider
android:authorities=com.paad.skeletondatabaseprovider/>

Publier lURI de votre fournisseur de contenu


Chaque fournisseur de contenu devrait exposer son autorit en utilisant une proprit
statique publique CONTENT_URI afin quelle soit plus facilement dcouverte. Cette
proprit devrait contenir un chemin daccs vers le contenu primaire:
public static final Uri CONTENT_URI =
Uri.parse(content://com.paad.skeletondatabaseprovider/elements);

Ces URI de contenu seront utilises par un rsolveur de contenus pour accder
votre fournisseur. Une requte utilisant lURI ci-dessus reprsente une requte de
toutes les lignes, alors quune URI se terminant par /<numro de ligne>, comme
dans lexemple ci-dessous, permet de crer une requte dune seule ligne:
content://com.paad.skeletondatabaseprovider/elements/5

Il est conseill de supporter ces deux formes daccs votre fournisseur. Le moyen
le plus simple dy parvenir consiste utiliser un UriMatcher pour analyser les URI
et dterminer leurs formes.
Le Listing 8.8 montre un squelette dimplmentation pour dfinir un analyseur dURI
qui dtermine si une URI est une requte de toutes les donnes ou dune simple ligne.
Listing8.8: Dfinition dun UriMatcher pour dterminer si une requte porte sur
tous les lments ou sur une seule ligne
// Cre les constantes utilises pour diffrencier les requtes
private static final int ALLROWS = 1;
private static final int SINGLE_ROW = 2;

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 275

03/08/12 07:26

276

Android 4

private static final UriMatcher uriMatcher;


// Remplit lobjet UriMatcher, o une URI se terminant par
// elements correspondra une requte de tous les lments et
// elements/[rowID] correspondra une requte dune seule ligne.
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(com.paad.skeletondatabaseprovider,
elements, ALLROWS);
uriMatcher.addURI(com.paad.skeletondatabaseprovider,
elements/#, SINGLE_ROW);

Vous pouvez utiliser la mme technique pour exposer des URI alternatives pour diffrents sous-ensembles de donnes ou diffrentes tables dans votre base, en utilisant
le mme fournisseur de contenu.
Maintenant que vous savez diffrencier les requtes sur toute une table et sur une seule
ligne, vous pouvez vous servir de la classe SQLiteQueryBuilder pour appliquer une
condition de slection supplmentaire votre requte, comme dans lexemple suivant:
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
// Si cest une requte dune seule ligne, on limite lensemble rsultat
// la ligne transmise en paramtre.
switch (uriMatcher.match(uri)) {
case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
queryBuilder.appendWhere(KEY_ID + = + rowID);
default: break;
}

Plus loin dans ce chapitre, nous verrons comment effectuer une requte laide de
SQLiteQueryBuilder .
Crer la base de donnes du fournisseur de contenu
Pour initialiser la source de donnes laquelle vous comptez accder au moyen
du fournisseur de contenu, redfinissez la mthode onCreate comme dans le Listing8.9. On utilise gnralement une implmentation de SQLiteOpenHelper, du type
dcrit dans la section prcdente, afin de reporter la cration et louverture de la
base de donnes tant quelle nest pas ncessaire.
Listing 8.9: Cration de la base de donnes du fournisseur de contenu
private MySQLiteOpenHelper myOpenHelper;
@Override
public boolean onCreate() {
// Construction de la base de donnes sous-jacente.
// Reporte louverture de la base tant que lon nen a pas besoin
// pour une requte ou une transaction.
myOpenHelper = new MySQLiteOpenHelper(getContext(),

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 276

03/08/12 07:26

Chapitre 8

Bases de donnes et fournisseurs de contenu

277

MySQLiteOpenHelper.DATABASE_NAME, null,
MySQLiteOpenHelper.DATABASE_VERSION);
return true;
}

Info
Lorsque votre application est lance, le gestionnaire onCreate de chacun de ses fournisseurs de contenu est excut dans le thread principal de lapplication.
Comme pour les exemples prcdents de la section prcdente, la meilleure approche
consiste utiliser un SQLiteOpenHelper pour reporter louverture (et, si ncessaire, la
cration) de la base de donnes sous-jacente tant quelle nest pas ncessaire dans les
mthodes query et transaction du fournisseur de contenu.
Pour des raisons defficacit, il est prfrable de laisser le fournisseur de contenu ouvert
tant que lapplication sexcute; il nest pas utile de fermer manuellement la base de
donnes. Si le systme a besoin de ressources, votre application sera tue et les bases
de donnes associes seront fermes.

Implmenter les requtes au fournisseur de contenu


Pour supporter les requtes votre fournisseur de contenu, vous devez implmenter
les mthodes query et getType qui seront utilises par les rsolveurs de contenu
pour accder aux donnes sous-jacentes sans connatre leur structure ou leur implmentation. Ces mthodes permettent aux applications de partager leurs donnes
sans devoir publier une interface spcifique chaque source de donnes.
Le scnario le plus courant consiste utiliser un fournisseur de contenu pour offrir
un accs une base de donnes SQLite, mais ces mthodes permettent daccder
nimporte quelle source de donnes (notamment des fichiers ou des variables
dinstance de lapplication).
Notez que lobjet UriMatcher sert affiner les requtes et les transactions, et que
lobjet SQLiteQueryBuilder est un outil pratique pour raliser des requtes de lignes.
Le Listing 8.10 est un squelette dimplmentation de requtes sur un fournisseur de
contenu utilisant une base de donnes SQLite sous-jacente.
Listing 8.10: Implmentation de requtes et de transactions sur un fournisseur de
contenu
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// Ouverture de la base de donnes.
SQLiteDatabase db;
try {
db = myOpenHelper.getWritableDatabase();
} catch (SQLiteException ex) {

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 277

03/08/12 07:26

278

Android 4

db = myOpenHelper.getReadableDatabase();
}
// remplacer par des instructions SQL valides si ncessaire.
String groupBy = null;
String having = null;
// Utilisation dun objet SQLiteQueryBuilder pour simplifier la
// construction de la requte.
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
// Si cest une requte de ligne, on limite lensemble rsultat
// la ligne passe en paramtre.
switch (uriMatcher.match(uri)) {
case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
queryBuilder.appendWhere(KEY_ID + = + rowID);
default: break;
}
// Prcise la table sur laquelle effectuer la requte.
// Il peut sagir dune table spcifique ou dune jointure.
queryBuilder.setTables(MySQLiteOpenHelper.DATABASE_TABLE);
// Excute la requte.
Cursor cursor = queryBuilder.query(db, projection, selection,
selectionArgs, groupBy, having,
sortOrder);
// Renvoie le curseur rsultat.
return cursor;
}

Si vous implmentez les requtes, vous devez galement prciser un type MIME
pour identifier les donnes renvoyes. Pour cela, redfinissez la mthode getType
pour quelle renvoie une chane dcrivant de faon unique le type de vos donnes.
Le type renvoy pourra tre de deux formes une pour les entres simples, lautre
pour toutes les entres:

lment simple:
vnd.android.cursor.item/vnd.<nomSocit>.<typeContenu>

Tous les lments:


vnd.android.cursor.dir/vnd.<nomSocit>.<typeContenu>

Le Listing 8.11 montre comment redfinir la mthode getType pour quelle renvoie
le type MIME correct en fonction de lURI.
Listing 8.11: Renvoi du type MIME dun fournisseur de contenu
@Override
public String getType(Uri uri) {

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 278

03/08/12 07:26

Chapitre 8

Bases de donnes et fournisseurs de contenu

279

// Renvoie une chane qui identifie le type MIME


// dune URI de fournisseur de contenu.
switch (uriMatcher.match(uri)) {
case ALLROWS:
return vnd.android.cursor.dir/vnd.paad.elemental;
case SINGLE_ROW:
return vnd.android.cursor.item/vnd.paad.elemental;
default:
throw new IllegalArgumentException(URI non reconnue : + uri); }
Transactions sur un fournisseur de contenu

Pour exposer les transactions de suppression, insertion et mise jour sur votre
fournisseur de contenu, implmentez les mthodes delete, insert, et update
correspondantes.
Comme la mthode query, ces mthodes seront utilises par le rsolveur de contenus
pour effectuer les transactions sur les donnes sous-jacentes sans connatre leur
implmentation ce qui permet aux applications de modifier des donnes entre
elles. Il est prfrable dutiliser la mthode notifyChange du rsolveur lorsque vous
effectuez des transactions qui modifient les donnes des tables. Celle-ci prviendra
les observateurs de contenu enregistrs pour un curseur donn ( laide de la mthode
Cursor.registerContentObserver) que la table sous-jacente (ou lune de ses lignes)
a t supprime, ajoute ou modifie. Comme pour les requtes, le cas dutilisation
le plus frquent est lexcution dune transaction sur une base de donnes SQLite,
bien que ce ne soit pas obligatoire. Le Listing8.12 est un squelette de code qui implmente des transactions sur un fournisseur de contenu utilisant une base de donnes
SQLite sous-jacente.
Listing8.12: Implmentation typique des transactions sur un fournisseur de contenu
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// Ouvre la base en lecture/criture pour la transaction.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();
// Si cest une URI de ligne, limite la suppression la ligne indique.
switch (uriMatcher.match(uri)) {
case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
selection = KEY_ID + = + rowID
+ (!TextUtils.isEmpty(selection) ?
AND ( + selection + ) : );
default: break;
}
//
//
//
if

Il faut indiquer une clause where pour renvoyer le nombre dlments


supprims. Pour supprimer toutes les lignes et renvoyer une valeur,
passez le paramtre "1".
(selection == null)
selection = 1;

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 279

03/08/12 07:26

280

Android 4

// Effectue la suppression.
int deleteCount = db.delete(MySQLiteOpenHelper.DATABASE_TABLE,
selection, selectionArgs);
// Prvient les observateurs que lensemble des donnes a t modifi.
getContext().getContentResolver().notifyChange(uri, null);
// Renvoie le nombre dlments supprims.
return deleteCount;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// Ouvre la base en lecture/criture pour la transaction.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();
// Pour ajouter des lignes vides la base en passant un objet
// ContentValues vide, vous devez utiliser lastuce du paramtre de
// colonne null pour indiquer le nom de la colonne qui peut tre mise
// null.
String nullColumnHack = null;
// Insre les valeurs dans la table.
long id = db.insert(MySQLiteOpenHelper.DATABASE_TABLE,
nullColumnHack, values);
// Construit et renvoie lURI de la ligne insre.
if (id > -1) {
// Construit et renvoie lURI de la ligne insre.
Uri insertedId = ContentUris.withAppendedId(CONTENT_URI, id);
// Prvient les observateurs que lensemble des donnes a t modifi.
getContext().getContentResolver().notifyChange(insertedId, null);
return insertedId;
} else
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// Ouvre la base en lecture/criture pour la transaction.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();
// Si cest une URI de ligne, limite la modification la ligne indique.
switch (uriMatcher.match(uri)) {
case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
selection = KEY_ID + = + rowID
+ (!TextUtils.isEmpty(selection) ?
AND ( + selection + ) : );
default: break;
}
// Effectue la modification.

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 280

03/08/12 07:26

Chapitre 8

Bases de donnes et fournisseurs de contenu

281

int updateCount = db.update(MySQLiteOpenHelper.DATABASE_TABLE,


values, selection, selectionArgs);
// Prvient les observateurs que lensemble des donnes a t modifi.
getContext().getContentResolver().notifyChange(uri, null);
return updateCount;
}

Info
La classe ContentUris fournit la mthode withAppendedId qui permet dajouter facilement
un identifiant de ligne la valeur CONTENT_URI dun fournisseur de contenu. Nous lutilisons dans le Listing8.12 pour construire lURI des lignes que lon vient dinsrer et
nous nous en servirons galement dans les sections suivantes pour dsigner une ligne
particulire au cours des requtes et des transactions sur la base de donnes.

Stocker des fichiers dans un fournisseur de contenu


Au lieu de stocker directement les gros fichiers dans votre fournisseur de contenu,
vous devriez les reprsenter sous la forme dURI pleinement qualifies pointant
vers leur emplacement sur le systme de fichiers.
Pour quune table puisse supporter les fichiers, vous devez inclure une colonne nomme _data qui contiendra le chemin vers le fichier reprsent par cette ligne. Cette
colonne ne devrait pas tre utilise par les applications clientes. Redfinissez le
gestionnaire openFile pour quil renvoie un objet ParcelFileDescriptor lorsque
lersolveur de contenus demande le fichier associ cette ligne.
Gnralement, un fournisseur de contenu comprend deux tables, une qui ne sert
qu stocker les fichiers externes, lautre qui contient une colonne destine lutilisateur, contenant une rfrence vers les lignes de la premire.
Le Listing 8.13 est un squelette de redfinition du gestionnaire openFile dun fournisseur de contenu. Ici, le nom du fichier sera reprsent par lidentifiant de la ligne
laquelle il appartient.
Listing 8.13: Stockage de fichiers dans un fournisseur de contenu
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
// Trouve lidentifiant de ligne et lutilise comme nom de fichier.
String rowID = uri.getPathSegments().get(1);
// Cre un objet fichier dans le rpertoire des fichiers externes de
// lapplication.
String picsDir = Environment.DIRECTORY_PICTURES;
File file = new File(getContext().getExternalFilesDir(picsDir), rowID);

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 281

03/08/12 07:26

282

Android 4

// Si le fichier nexiste pas, on le cre.


if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
Log.d(TAG, chec de cration du fichier : + e.getMessage());
}
}
// Traduit le mode douvertur dans le mode correspondant pour le
// ParcelFileDescriptor
int fileMode = 0;
if (mode.contains(w))
fileMode |= ParcelFileDescriptor.MODE_WRITE_ONLY;
if (mode.contains(r))
fileMode |= ParcelFileDescriptor.MODE_READ_ONLY;
if (mode.contains(+))
fileMode |= ParcelFileDescriptor.MODE_APPEND;
// Renvoie un ParcelFileDescriptor qui reprsente le fichier.
return ParcelFileDescriptor.open(file, fileMode);
}

Info
Les fichiers associs des lignes dans la base de donnes tant stocks lextrieur
de celle-ci, il est important de rflchir leffet que devrait avoir la suppression dune
ligne sur le fichier sous-jacent.

Squelette dimplmentation dun fournisseur de contenu


Le Listing 8.14 est un squelette dimplmentation dun fournisseur de contenu. Ilutilise un objet SQLiteOpenHelper pour grer la base et transmet simplement chaque
requte ou transaction directement la base de donnes SQLite sous-jacente.
Listing 8.14: Squelette dimplmentation dun fournisseur de contenu
import
import
import
import
import
import
import
import
import
import
import
import
import

android.content.ContentProvider;
android.content.ContentUris;
android.content.ContentValues;
android.content.Context;
android.content.UriMatcher;
android.database.Cursor;
android.database.sqlite.SQLiteDatabase;
android.database.sqlite.SQLiteDatabase.CursorFactory;
android.database.sqlite.SQLiteOpenHelper;
android.database.sqlite.SQLiteQueryBuilder;
android.net.Uri;
android.text.TextUtils;
android.util.Log;

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 282

03/08/12 07:26

Chapitre 8

Bases de donnes et fournisseurs de contenu

283

public class MyContentProvider extends ContentProvider {


public static final Uri CONTENT_URI =
Uri.parse(content://com.paad.skeletondatabaseprovider/elements) ;
// Cre les constantes utilises pour diffrencier les requtes
private static final int ALLROWS = 1;
private static final int SINGLE_ROW = 2;
private static final UriMatcher uriMatcher;
// Remplit lobjet UriMatcher, o une URI se terminant par
// elements correspondra une requte de tous les lments et
// elements/[rowID] correspondra une requte dune seule ligne.
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(com.paad.skeletondatabaseprovider,
elements, ALLROWS);
uriMatcher.addURI(com.paad.skeletondatabaseprovider,
elements/#, SINGLE_ROW);
}
// Le nom de la colonne index (cl) utilise par les clauses where.
public static final String KEY_ID = _id;
// Le nom et lindice de chaque colonne de la base.
// Ils devraient tre vocateurs.
public static final String KEY_COLUMN_1_NAME = KEY_COLUMN_1_NAME;
// faire : crer des champs publics pour chaque colonne de la table.
// Variable SQLiteOpenHelper
private MySQLiteOpenHelper myOpenHelper;
@Override
public boolean onCreate() {
// Construit la base de donnes sous-jacente.
// Reporte louverture de la base tant que lon nen a pas
// besoin pour une requte ou une transaction.
myOpenHelper = new MySQLiteOpenHelper(getContext(),
MySQLiteOpenHelper.DATABASE_NAME, null,
MySQLiteOpenHelper.DATABASE_VERSION);
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// Ouverture de la base de donnes.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();;
// remplacer par des instructions SQL valides si ncessaire.
String groupBy = null;
String having = null;
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(MySQLiteOpenHelper.DATABASE_TABLE);

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 283

03/08/12 07:26

284

Android 4

// Si cest une requte de ligne, on limite lensemble rsultat


// la ligne passe en paramtre.
switch (uriMatcher.match(uri)) {
case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
queryBuilder.appendWhere(KEY_ID + = + rowID);
default: break;
}
// Excute la requte.
Cursor cursor = queryBuilder.query(db, projection, selection,
selectionArgs, groupBy, having,
sortOrder);
// Renvoie le curseur rsultat.
return cursor;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// Ouvre la base en lecture/criture pour la transaction.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();
// Si cest une URI de ligne, limite la suppression la ligne indique.
switch (uriMatcher.match(uri)) {
case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
selection = KEY_ID + = + rowID
+ (!TextUtils.isEmpty(selection) ?
AND ( + selection + ) : );
default: break;
}
//
//
//
if

Il faut indiquer une clause where pour renvoyer le nombre dlments


supprims. Pour supprimer toutes les lignes et renvoyer une valeur,
passez le paramtre "1".
(selection == null)
selection = 1;

// Effectue la suppression.
int deleteCount = db.delete(MySQLiteOpenHelper.DATABASE_TABLE,
selection, selectionArgs);
// Prvient les observateurs que lensemble des donnes a t modifi.
getContext().getContentResolver().notifyChange(uri, null);
// Renvoie le nombre dlments supprims.
return deleteCount;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// Ouvre la base en lecture/criture pour la transaction.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();
// Pour ajouter des lignes vides la base en passant un objet
// ContentValues vide, vous devez utiliser lastuce du paramtre de

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 284

03/08/12 07:26

Chapitre 8

Bases de donnes et fournisseurs de contenu

285

// colonne null pour indiquer le nom de la colonne qui peut tre mise
// null.
String nullColumnHack = null;
// Insre les valeurs dans la table.
long id = db.insert(MySQLiteOpenHelper.DATABASE_TABLE,
nullColumnHack, values);
// Construit et renvoie lURI de la ligne insre.
if (id > -1) {
// Construit et renvoie lURI de la ligne insre.
Uri insertedId = ContentUris.withAppendedId(CONTENT_URI, id);
// Prvient les observateurs que lensemble des donnes a t modifi.
getContext().getContentResolver().notifyChange(insertedId, null);
return insertedId;
} else
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// Ouvre la base en lecture/criture pour la transaction.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();
// Si cest une URI de ligne, limite la modification la ligne indique.
switch (uriMatcher.match(uri)) {
case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
selection = KEY_ID + = + rowID
+ (!TextUtils.isEmpty(selection) ?
AND ( + selection + ) : );
default: break;
}
// Effectue la modification.
int updateCount = db.update(MySQLiteOpenHelper.DATABASE_TABLE,
values, selection, selectionArgs);
// Prvient les observateurs que lensemble des donnes a t modifi.
getContext().getContentResolver().notifyChange(uri, null);
return updateCount;
}
@Override
public String getType(Uri uri) {
// Renvoie une chane qui identifie le type MIME
// dune URI de fournisseur de contenu.
switch (uriMatcher.match(uri)) {
case ALLROWS:
return vnd.android.cursor.dir/vnd.paad.elemental;
case SINGLE_ROW:
return vnd.android.cursor.item/vnd.paad.elemental;
default:

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 285

03/08/12 07:26

286

Android 4

throw new IllegalArgumentException(URI non reconnue : + uri);


}
}
private static class MySQLiteOpenHelper extends SQLiteOpenHelper {
// [ ... Implmentation de SQLiteOpenHelper ... ]
}
}

Utiliser les fournisseurs de contenu


La section suivante prsente la classe ContentResolver et son utilisation pour effectuer
des requtes et des transactions sur un fournisseur de contenu.
Introduction aux rsolveurs de contenu
Chaque application contient une instance de ContentResolver accessible par la
mthode getContentResolver.
ContentResolver cr = getContentResolver();

Lorsque les fournisseurs de contenu sont utiliss pour exposer des donnes, les
rsolveurs de contenu sont les classes correspondantes permettant dinterroger et
deffectuer des transactions sur ces fournisseurs. Tandis que les fournisseurs de
contenu offrent une abstraction par rapport aux donnes sous-jacentes, les rsolveurs
de contenu fournissent une abstraction par rapport au fournisseur qui est interrog
ou manipul.
Le rsolveur de contenu inclut les mthodes pour les requtes et les transactions
correspondant celles qui ont t dfinies dans vos fournisseurs. Il na pas besoin
de connatre limplmentation des fournisseurs de contenu avec lesquels il interagit
chaque mthode de requte ou transaction prend simplement en paramtre une
URI qui indique le fournisseur de contenu concern.
Une URI de fournisseur de contenu est son autorit dfinie dans son manifeste et
gnralement publie sous la forme dune constante statique de limplmentation du
fournisseur.
Les fournisseurs de contenu acceptent en gnral deux formes dURI, lune pour les
requtes sur toutes les donnes et lautre pour les requtes sur une seule ligne. Dans
cette dernire, un /<rowID> est ajout lURI de base.
Effectuer des requtes
Les requtes sur un fournisseur de contenu sont trs semblables celles effectues
sur une base de donnes. Les rsultats sont renvoys sous forme de curseurs de la
faon dcrite plus haut dans ce chapitre.
2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 286

03/08/12 07:26

Chapitre 8

Bases de donnes et fournisseurs de contenu

287

Vous pouvez extraire les valeurs dun curseur en utilisant les mmes techniques que
celles dcrites dans la section "Extraire les rsultats dun curseur".
Utilisez la mthode query sur lobjet ContentResolver en lui passant les lments
suivants:

LURI des donnes du fournisseur de contenu que vous voulez interroger.

Une projection numrant les colonnes que vous voulez inclure dans le rsultat.

Une clause where dfinissant les lignes ramener. Vous pouvez inclure des
jokers ? qui seront remplacs par les valeurs passes par le paramtre des
arguments de slection.

Un tableau darguments de slection qui remplaceront les? de la clause where.

Une chane dcrivant lordre des lignes ramenes.

Le Listing8.15 montre lutilisation dun rsolveur de contenu pour interroger un


fournisseur de contenu.
Listing8.15: Interrogation dun fournisseur de contenu laide dun rsolveur de
contenu
// Rcuprer le rsolveur de contenu
ContentResolver cr = getContentResolver();
// Indique la projection des colonnes du rsultat. Renvoie
// lensemble minimal de colonnes ncessaires aux besoins.
String[] result_columns = new String[] {
MyHoardContentProvider.KEY_ID,
MyHoardContentProvider.KEY_GOLD_HOARD_ACCESSIBLE_COLUMN,
MyHoardContentProvider.KEY_GOLD_HOARDED_COLUMN };
// Dfinition de la clause where qui limitera le nombre de lignes
// du rsultat.
String where = MyHoardContentProvider.KEY_GOLD_HOARD_ACCESSIBLE_COLUMN
+ = + 1;
// remplacer par les instructions SQL valides en fonction des besoins.
String whereArgs[] = null;
String order = null;
// Renvoie les lignes indiques.
Cursor resultCursor = cr.query(MyHoardContentProvider.CONTENT_URI,
result_columns, where, whereArgs, order);

Dans cet exemple, la requte utilise les constantes statiques fournies par la classe
MyHoardContentProvider, mais une application tierce aurait trs bien pu excuter la
mme requte pourvu quelle connaisse lURI du contenu et les noms des colonnes,
et quelle dispose des permissions appropries.

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 287

03/08/12 07:26

288

Android 4

La plupart des fournisseurs de contenu incluent galement un raccourci permettant


daccder une ligne prcise en ajoutant son identifiant lURI. Vous pouvez utiliser
la mthode withAppendedId de la classe ContentUris pour simplifier la cration de
ce raccourci, comme le montre le Listing8.16.
Listing 8.16: Accs une ligne particulire dans un fournisseur de contenu
// Rcupration du rsolveur de contenu.
ContentResolver cr = getContentResolver();
// Indique la projection des colonnes du rsultat. Renvoie
// lensemble minimal de colonnes ncessaires aux besoins.
String[] result_columns = new String[] {
MyHoardContentProvider.KEY_ID,
MyHoardContentProvider.KEY_GOLD_HOARD_NAME_COLUMN,
MyHoardContentProvider.KEY_GOLD_HOARDED_COLUMN };
// Ajoute un identifiant de ligne lURI pour accder une ligne
// particulire.
Uri rowAddress =
ContentUris.withAppendedId(MyHoardContentProvider.CONTENT_URI,
rowId);
// Ces
String
String
String

variables sont null car on ne demande quune seule ligne.


where = null;
whereArgs[] = null;
order = null;

// Renvoie la ligne indique.


Cursor resultCursor = cr.query(rowAddress,
result_columns, where, whereArgs, order);

Pour extraire les valeurs dun curseur, utilisez les mmes techniques que celles que
nous avons dcrites plus haut, en vous servant des mthodes moveTo<endroit> et
get<type> pour extraire les valeurs de la ligne et de la colonne.
Le Listing8.17 tend le code du Listing8.16 en parcourant un curseur pour afficher
le nom du plus gros magot.
Listing 8.17: Extraction les valeurs du curseur dun fournisseur de contenu
loat largestHoard = 0f;
String hoardName = Pas de magot;
// Trouve les indices des colonnes utilises.
int GOLD_HOARDED_COLUMN_INDEX = resultCursor.getColumnIndexOrThrow(
MyHoardContentProvider.KEY_GOLD_HOARDED_COLUMN);
int HOARD_NAME_COLUMN_INDEX = resultCursor.getColumnIndexOrThrow(
MyHoardContentProvider.KEY_GOLD_HOARD_NAME_COLUMN);
// Parcourt les lignes du curseur.
// Le curseur est plac avant la premire ligne lorsquil est initialis.

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 288

03/08/12 07:26

Chapitre 8

Bases de donnes et fournisseurs de contenu

289

// On peut donc simplement vrifier quil existe une ligne "suivante".


// Si le curseur est vide, ce test renverra false.
while (resultCursor.moveToNext()) {
float hoard = resultCursor.getFloat(GOLD_HOARDED_COLUMN_INDEX);
if (hoard > largestHoard) {
largestHoard = hoard;
hoardName = resultCursor.getString(HOARD_NAME_COLUMN_INDEX);
}
}
// Ferme le curseur lorsque lon nen a plus besoin.
resultCursor.close();

Lorsque lon a termin dutiliser le curseur, il est important de le fermer pour viter
les fuites mmoire et pour rduire les ressources consommes par lapplication.
Vous verrez dautres exemples plus loin dans ce chapitre lorsque nous prsenterons
les fournisseurs de contenu natifs Android.
Attention
Les requtes sur les bases de donnes peuvent durer un certain temps. Par dfaut, le
rsolveur de contenu excutera les requtes et les autres transactions dans le thread
principal de lapplication.
Pour garantir que votre application restera ractive, vous devez excuter toutes les
requtes de faon asynchrone, comme nous lexpliquons dans la section qui suit.

Faire des requtes asynchrones avec un chargeur de curseur


Les oprations sur les bases de donnes pouvant durer un certain temps, il est particulirement important que les requtes sur les bases de donnes et les fournisseurs
de contenu ne sexcutent pas dans le thread principal de lapplication.
Grer les curseurs pour quils se synchronisent correctement avec le thread de
linterface utilisateur tout en sassurant que les requtes aient lieu en arrire-plan
peut tre une tche assez complique. Pour la simplifier, Android 3.0 (API level
11) a introduit la classe Loader, qui permet de dfinir des chargeurs. Ceux-ci sont
dsormais galement disponibles dans la bibliothque support, ce qui autorise donc
leur utilisation sur toutes les anciennes plateformes Android jusqu la version 1.6.
Introduction aux chargeurs

Les chargeurs sont disponibles pour chaque activit et fragment via la classe
LoaderManager. Ils sont conus pour charger les donnes de faon asynchrone et
pour surveiller les modifications de la source de donnes sous-jacente.
Bien que les chargeurs puissent tre implments pour charger nimporte quelle sorte
de donnes partir de nimporte quelle source, la classe CursorLoader mrite une
attention spciale. Un chargeur de curseur permet en effet deffectuer des requtes
2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 289

03/08/12 07:26

290

Android 4

asynchrones sur les fournisseurs de contenu et renvoie un curseur et les notifications


de chaque mise jour du fournisseur sous-jacent.
Info
Pour que le code reste concis, tous les exemples de ce chapitre nutilisent pas un chargeur
de curseur pour effectuer des requtes un fournisseur de contenu. Pour vos applications, nous vous recommandons toutefois de toujours utiliser un chargeur de curseur
pour grer les curseurs dans vos activits et vos fragments.

Utiliser un chargeur de curseur

Un chargeur de curseur gre toutes les tches ncessaires lutilisation dun curseur
dans une activit ou un fragment, ce qui rend obsoltes les mthodes managedQuery
et startManagingCursor dActivity.
Ces tches incluent notamment la gestion du cycle de vie des curseurs pour garantir
quils seront ferms lorsque lactivit sera termine.
Les chargeurs de curseurs surveillent galement les modifications du contenu sousjacent: vous navez donc plus besoin dimplmenter vos propres observateurs de
contenu.
Implmenter les fonctions de rappel dun chargeur de curseur

Pour utiliser un chargeur de curseur, crez une nouvelle implmentation de


LoaderManager.LoaderCallbacks. Les fonctions de rappel dun chargeur tant implmentes au moyen de mthodes gnriques, vous devez prciser le type explicite qui
sera charg ici, le type Cursor lorsque vous implmentez votre propre fonction
de rappel:
LoaderManager.LoaderCallbacks<Cursor> loaderCallback =
new LoaderManager.LoaderCallbacks<Cursor>() {

Si vous navez besoin que dune seule implmentation de chargeur dans votre activit
ou votre fragment, la dmarche classique consiste implmenter cette interface dans
ce composant.
Les fonctions de rappel dun chargeur consistent en trois gestionnaires:
OnCreateLoader.

Appel lorsque le chargeur est initialis, ce gestionnaire cre


et renvoie un nouveau chargeur de curseur. Les paramtres du constructeur
de CursorLoader sont les rpliques de ceux dont on a besoin pour excuter
une requte en utilisant le rsolveur de contenu car, lorsque ce gestionnaire est
excut, ces paramtres servent excuter une requte.

OnLoadFinished.

Lorsque le LoaderManager a termin la requte asynchrone,


ce gestionnaire est appel avec le curseur rsultat en paramtre. On utilise ce
curseur pour mettre jour les adaptateurs et les autres lments de linterface
utilisateur.
2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 290

03/08/12 07:26

Chapitre 8

Bases de donnes et fournisseurs de contenu

291

OnLoaderReset.

Appel lorsque le LoaderManager rinitialise le chargeur de


curseur. Dans ce gestionnaire, vous devriez librer toutes les rfrences aux
donnes renvoyes par la requte et rinitialiser linterface utilisateur en consquence. Le curseur sera ferm par le LoaderManager: il ne faut donc pas essayer
de le fermer manuellement.

Attention
onLoadFinished et onLoaderReset ne sont pas synchronises avec le thread de linterface

utilisateur. Si vous voulez modifier directement les lments de linterface, vous devrez
dabord vous synchroniser avec le thread de linterface utilisateur laide dun gestionnaire ou dun mcanisme similaire. La synchronisation avec le thread de linterface
utilisateur sera tudie en dtail au Chapitre9.

Le Listing 8.18 est un squelette dimplmentation des fonctions de rappel dun


chargeur de curseur.
Listing 8.18: Implmentation des fonctions de rappel dun chargeur
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// Construit la nouvelle requte sous le forme dun chargeur.
// Le paramtre id permet de construire et renvoyer des chargeurs
// diffrents..
String[] projection = null;
String where = null;
String[] whereArgs = null;
String sortOrder = null;
// URI de la requte.
Uri queryUri = MyContentProvider.CONTENT_URI;
// Cre le chargeur de curseur.
return new CursorLoader(DatabaseSkeletonActivity.this, queryUri,
projection, where, whereArgs, sortOrder);
}
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
// Remplace le curseur rsultat affich par le CursorAdapter par
// le nouvel ensemble rsultat.
adapter.swapCursor(cursor);
// Ce gestionnaire ntant pas synchronis avec le thread de
// linterface utilisateur, vous devez le synchroniser avant de
// modifier directement les lments de linterface.
}
public void onLoaderReset(Loader<Cursor> loader) {
// Supprime le curseur rsultat existant du ListAdapter.
adapter.swapCursor(null);
// Ce gestionnaire ntant pas synchronis avec le thread de
// linterface utilisateur, vous devez le synchroniser avant de
// modifier directement les lments de linterface.
}

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 291

03/08/12 07:26

292

Android 4

Initialiser et relancer le chargeur de curseur

Chaque activit ou fragment donne laccs son LoadManager via un appel


getLoaderManager:
LoaderManager loaderManager = getLoaderManager();

Pour initialiser un nouveau chargeur, appelez la mthode initLoader du LoaderManager,


en lui passant en paramtre une rfrence votre implmentation de LoaderCallbacks,
un Bundle de paramtres facultatif et un identifiant de chargeur:
Bundle args = null;
loaderManager.initLoader(LOADER_ID, args, myLoaderCallbacks);

Cette initialisation a gnralement lieu dans la mthode onCreate de lactivit hte


(ou dans la mthode onActivityCreated si vous utilisez un fragment).
Dans la plupart des cas, ce sera suffisant: le LoaderManager grera le cycle de vie de
tous les chargeurs que vous initialisez, ainsi que les requtes et les curseurs sous-jacents.
Il prendra galement en charge les modifications dans les rsultats des requtes.
Lorsquun chargeur a t cr, des appels rpts initLoader renverront simplement
le chargeur existant. Si vous voulez le supprimer et en recrer un autre, utilisez la
mthode restartLoader:
loaderManager.restartLoader(LOADER_ID, args, myLoaderCallbacks);

Ceci est gnralement ncessaire lorsque les paramtres de la requte changent dans
le cas de recherches ou de modifications de lordre du tri, notamment.
Ajouter, mettre jour et supprimer du contenu
Pour effectuer des transactions sur des fournisseurs de contenu, utilisez les mthodes
delete, update et insert sur lobjet ContentResolver. Comme les requtes, sauf si
elles sont dplaces dans un thread de travail, les transactions sur un fournisseur de
contenu sexcuteront dans le thread principal de lapplication.
Info
Les oprations sur les bases de donnes pouvant durer un certain temps, il est important
dexcuter chaque transaction de faon asynchrone.

Insertions

Le rsolveur de contenu propose deux mthodes pour insrer de nouveaux enregistrements dans un fournisseur de contenu, insert et bulkInsert. Toutes les deux prennent
en paramtre lURI du fournisseur de contenu dans lequel vous insrez llment. La
premire prend un objet ContentValues en entre et la seconde, un tableau.
La mthode insert renvoie une URI vers lenregistrement insr alors que bulkInsert
renvoie le nombre de lignes ajoutes.
2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 292

03/08/12 07:26

Chapitre 8

Bases de donnes et fournisseurs de contenu

293

Le Listing8.19 montre lusage des mthodes insert et bulkInsert.


Listing8.19: Insertion de nouvelles lignes dans un fournisseur de contenu
// Rcupre le rsolveur de contenu.
ContentResolver cr = getContentResolver();
// Cre une nouvelle ligne.
ContentValues newValues = new ContentValues();
// Affecte des valeurs chaque ligne.
newValues.put(MyHoardContentProvider.KEY_GOLD_HOARD_NAME_COLUMN,
hoardName);
newValues.put(MyHoardContentProvider.KEY_GOLD_HOARDED_COLUMN,
hoardValue);
newValues.put(MyHoardContentProvider.KEY_GOLD_HOARD_ACCESSIBLE_COLUMN,
hoardAccessible);
[ ... Rpter pour chaque paire colonne/valeur ... ]
// Insere la ligne dans la table
Uri myRowUri = cr.insert(MyHoardContentProvider.CONTENT_URI,
newValues);

Suppressions

Pour supprimer un enregistrement, appelez delete sur le rsolveur de contenu en


passant lURI de la ligne supprimer. Vous pouvez galement indiquer une clause
where pour supprimer plusieurs lignes (voir le Listing8.20).
Listing8.20: Suppression des enregistrements dun fournisseur de contenu
// Prcise la clause where qui dtermine la ou les lignes supprimer.
// Indique les paramtres where en fonction des besoins.
String where = MyHoardContentProvider.KEY_GOLD_HOARDED_COLUMN +
=0;
String whereArgs[] = null;
// Rcupre le rsolveur de contenu.
ContentResolver cr = getContentResolver();
// Supprime les lignes correspondantes
int deletedRowCount =
cr.delete(MyHoardContentProvider.CONTENT_URI, where, whereArgs);

Mises jour

Les mises jour des lignes dun fournisseur de contenu sont effectues par la mthode
update du rsolveur de contenu. Cette mthode reoit lURI du fournisseur de contenu
cible, un objet ContentValues contenant les valeurs des colonnes mettre jour et
une clause where qui indique quelles lignes mettre jour.

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 293

03/08/12 07:26

294

Android 4

Lorsque la mise jour est effectue, chaque ligne concerne par la clause where est
mise jour par les ContentValues, et le nombre de mises jour russies est renvoy.
Vous pouvez aussi choisir de modifier une ligne spcifique en indiquant son URI
unique, comme dans le Listing8.21.
Listing8.21: Mise jour dun enregistrement dans un fournisseur de contenu
// Cre le contenu modifi de la ligne, en affectant des valeurs
// chaque ligne.
ContentValues updatedValues = new ContentValues();
updatedValues.put(MyHoardContentProvider.KEY_GOLD_HOARDED_COLUMN,
newHoardValue);
// [ ... Rpter pour chaque colonne modifier ... ]
// Cre une URI pour dsigner une ligne prcise.
Uri rowURI =
ContentUris.withAppendedId(MyHoardContentProvider.CONTENT_URI,
hoardId);
// On indique une ligne spcifique : il ny a donc pas besoin de clause
// de slection.
String where = null;
String whereArgs[] = null;
// Rcupre le rsolveur de contenu.
ContentResolver cr = getContentResolver();
// Modifie la ligne indique.
int updatedRowCount =
cr.update(rowURI, updatedValues, where, whereArgs);

Accder des fichiers stocks dans des fournisseurs de contenu


Les fournisseurs de contenu reprsentent les gros fichiers sous forme dURI qualifies
et non comme des fichiers binaires bruts (blobs). Cependant, cette reprsentation est
cache lorsque lon utilise le rsolveur de contenu.
Pour insrer un fichier dans un fournisseur de contenu ou accder un fichier existant,
utilisez respectivement les mthodes openOutputStream et openInputStream du
rsolveur de contenu en leur passant lURI de la ligne du fournisseur de contenu qui
contient le fichier concern. Le fournisseur interprtera votre requte et renverra un
flux en criture ou en lecture vers le fichier demand, comme le montre le Listing8.22.
Listing8.22: Lecture et criture des fichiers partir ou dans un fournisseur de
contenu
public void addNewHoardWithImage(String hoardName, float hoardValue,
boolean hoardAccessible, Bitmap bitmap) {
// Cre une ligne de valeurs insrer.
ContentValues newValues = new ContentValues();

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 294

03/08/12 07:26

Chapitre 8

Bases de donnes et fournisseurs de contenu

295

// Affecte des valeurs chaque ligne.


newValues.put(MyHoardContentProvider.KEY_GOLD_HOARD_NAME_COLUMN,
hoardName);
newValues.put(MyHoardContentProvider.KEY_GOLD_HOARDED_COLUMN,
hoardValue);
newValues.put( MyHoardContentProvider.KEY_GOLD_HOARD_ACCESSIBLE_COLUMN,
hoardAccessible);
// Rcupre le rsolveur de contenu.
ContentResolver cr = getContentResolver();
// Insre la ligne dans la table.
Uri myRowUri =
cr.insert(MyHoardContentProvider.CONTENT_URI, newValues);
try {
// Ouvre un flux en criture en utilisant lURI de la nouvelle ligne.
OutputStream outStream = cr.openOutputStream(myRowUri);
// Compresse le bitmap et le sauvegarde dans le fournisseur.
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outStream);
}
catch (FileNotFoundException e) {
Log.d(TAG, Aucun fichier trouv pour cet enregistrement.);
}
}
public Bitmap getHoardImage(long rowId) {
Uri myRowUri =
ContentUris.withAppendedId(MyHoardContentProvider.CONTENT_URI,
rowId);
try {
// Ouvre un flux en lecture en utilisant lURI de la nouvelle ligne.
InputStream inStream = getContentResolver().openInputStream(myRowUri);
// Copie du bitmap.
Bitmap bitmap = BitmapFactory.decodeStream(inStream);
return bitmap;
}
catch (FileNotFoundException e) {
Log.d(TAG, Aucun fichier trouv pour cet enregistrement.);
}
return null;
}

Cration dune base de donnes et dun fournisseur de contenu pour


la liste de tches
Au Chapitre4, vous avez cr une application pour grer une liste de tches. Nous
allons ici crer une base de donnes et un fournisseur de contenu pour sauvegarder
chaque lment de la liste.
2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 295

03/08/12 07:26

296

Android 4

1. Commencez par crer une classe ToDoContentProvider qui tend


ContentProvider. Elle servira hberger la base de donnes en utilisant un
SQLiteOpenHelper et grer les interactions de la base. Ajoutez-lui des bauches
pour les mthodes onCreate, query, update, insert, delete et getType, ainsi
quun squelette dimplmentation dun SQLiteOpenHelper:
package com.paad.todolist;
import
import
import
import
import
import
import
import
import
import
import
import
import

android.content.ContentProvider;
android.content.ContentUris;
android.content.ContentValues;
android.content.Context;
android.content.UriMatcher;
android.database.Cursor;
android.database.sqlite.SQLiteDatabase;
android.database.sqlite.SQLiteQueryBuilder;
android.database.sqlite.SQLiteDatabase.CursorFactory;
android.database.sqlite.SQLiteOpenHelper;
android.net.Uri;
android.text.TextUtils;
android.util.Log;

public class ToDoContentProvider extends ContentProvider {


@Override
public boolean onCreate() {
return false;
}
@Override
public String getType(Uri url) {
return null;
}
@Override
public Cursor query(Uri url, String[] projection, String selection,
String[] selectionArgs, String sort) {
return null;
}
@Override
public Uri insert(Uri url, ContentValues initialValues) {
return null;
}
@Override
public int delete(Uri url, String where, String[] whereArgs) {
return 0;
}
@Override
public int update(Uri url, ContentValues values,
String where, String[]wArgs) {
return 0;
}

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 296

03/08/12 07:26

Chapitre 8

Bases de donnes et fournisseurs de contenu

297

private static class MySQLiteOpenHelper extends SQLiteOpenHelper {


public MySQLiteOpenHelper(Context context, String name,
CursorFactory factory, int version) {
super(context, name, factory, version);
}
// Appele lorsque aucune base nexiste sur le disque et que la classe
// helper a besoin den crer une nouvelle.
@Override
public void onCreate(SQLiteDatabase db) {
// faire: crer les tables de la base.
}
// Appele sil y a un conflit de version de la base, ce qui signifie
// que la version de la base de donnes sur le disque doit tre mise
// jour avec la version courante.
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// faire: mettre jour la version de la base.
}
}
}

2. Publiez lURI de ce fournisseur. Cette URI servira accder ce fournisseur


de contenu depuis dautres composants dapplication via le rsolveur de contenu.
public static final Uri CONTENT_URI =
Uri.parse(content://com.paad.todoprovider/todoitems);

3. Crez des variables statiques publiques qui dfinissent les noms des colonnes.
Elles serviront lobjet MySQLiteOpenHelper pour crer la base et aux autres
composants dapplication pour extraire des valeurs de vos requtes.
public static final String KEY_ID = _id;
public static final String KEY_TASK = task;
public static final String KEY_CREATION_DATE = creation_date;

4. Dans MysSQLiteOpenHelper, crez des variables pour stocker le nom et la version


de la base de donnes ainsi que le nom de la table de la liste de tches.
private static final String DATABASE_NAME = todoDatabase.db;
private static final int DATABASE_VERSION = 1;
private static final String DATABASE_TABLE = todoItemTable;

5. Toujours dans MysSQLiteOpenHelper , rcrivez les mthodes onCreate et


onUpgrade pour traiter la cration de la base de donnes en utilisant les colonnes
de ltape3 et des instructions de mise jour standard.
// Instruction SQL pour crer une base de donnes. .
private static final String DATABASE_CREATE = create table +
DATABASE_TABLE + ( + KEY_ID +
integer primary key autoincrement, +

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 297

03/08/12 07:26

298

Android 4

KEY_TASK + text not null, +


KEY_CREATION_DATE + long);;
// Appele lorsque aucune base nexiste sur le disque et que la classe
// helper a besoin den crer une nouvelle.
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE);
}
// Appele sil y a un conflit de version de la base, ce qui signifie
// que la version de la base de donnes sur le disque doit tre mise
// jour avec la version courante.
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Enregistre dans le journal le changement de version.
Log.w(TaskDBAdapter, Le passage de la version +
oldVersion + la version +
newVersion + , dtruira les anciennes donnes.);
// Mise jour de la base existante pour se conformer la nouvelle version.
// On peut grer plusieurs anciennes versions en comparant les valeurs de
// oldVersion et newVersion
// Le cas le plus simple consiste supprimer lancienne table et en crer
// une nouvelle.
db.execSQL(DROP TABLE IF IT EXISTS + DATABASE_TABLE);
// Cration dune nouvelle table.
onCreate(db);
}

6. Revenez ToDoContentProvider et ajoutez-lui une variable prive pour stocker


une instance de la classe MySQLiteOpenHelper que vous crerez dans le gestionnaire onCreate.
private MySQLiteOpenHelper myOpenHelper;
@Override
public boolean onCreate() {
// Construit la base de donnes sous-jacente.
// Reporte louverture de la base tant que lon nen a pas besoin pour
// une requte ou une transaction.
myOpenHelper = new MySQLiteOpenHelper(getContext(),
MySQLiteOpenHelper.DATABASE_NAME, null,
MySQLiteOpenHelper.DATABASE_VERSION);
return true;
}

7. Toujours dans le fournisseur de contenu, crez un nouvel objet UriMatcher pour


permettre votre fournisseur de contenu de faire la diffrence entre une requte
sur la table entire et une requte sur une ligne particulire. Utilisez-le dans le
gestionnaire getType pour renvoyer le type MIME correct en fonction du type
de la requte.

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 298

03/08/12 07:26

Chapitre 8

Bases de donnes et fournisseurs de contenu

299

private static final int ALLROWS = 1;


private static final int SINGLE_ROW = 2;
private static final UriMatcher uriMatcher;
// Remplit lobjet UriMatcher, o une URI se terminant par todoitems
// correspondra une requte de toutes les tches et o todoitems/[rowID]
// reprsente une seule ligne .
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(com.paad.todoprovider, todoitems, ALLROWS);
uriMatcher.addURI(com.paad.todoprovider, todoitems/#, SINGLE_ROW);
}
@Override
public String getType(Uri uri) {
// Renvoie une chane identifiant le type MIME de lURI
// dun fournisseur de contenu.
switch (uriMatcher.match(uri)) {
case ALLROWS: return vnd.android.cursor.dir/vnd.paad.todos;
case SINGLE_ROW: return vnd.android.cursor.item/vnd.paad.todos;
default: throw new IllegalArgumentException(URI non supporte : + uri);
}
}

8. Implmentez lbauche de la mthode query. Commencez par demander une


instance de la base de donnes avant de construire une requte utilisant les
paramtres passs. Dans cette instance, vous ne devez appliquer les mmes
paramtres de requte qu la base de donnes sous-jacente ne modifiez la
requte que pour prendre en compte la possibilit quune URI dsigne une seule
ligne.
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// Ouvre une base de donnes en lecture seule.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();
// Remplacer par des instructions SQL valides si ncessaire.
String groupBy = null;
String having = null;
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(MySQLiteOpenHelper.DATABASE_TABLE);
// Si cest une requte de ligne, limite lensemble rsultat la ligne
// passe en paramtre.
switch (uriMatcher.match(uri)) {
case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
queryBuilder.appendWhere(KEY_ID + = + rowID);
default: break;
}

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 299

03/08/12 07:26

300

Android 4

Cursor cursor = queryBuilder.query(db, projection, selection,


selectionArgs, groupBy, having, sortOrder);
return cursor;
}

9. Implmentez les mthodes delete, insert et update selon la mme approche


passez les paramtres reus tout en traitant le cas particulier des URI de ligne
simple.
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// Ouvre une base en lecture/criture pour la transaction.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();
// Si cest une URI de ligne, limite la suppression la ligne indique.
switch (uriMatcher.match(uri)) {
case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
selection = KEY_ID + = + rowID
+ (!TextUtils.isEmpty(selection) ?
AND ( + selection + ) : );
default: break;
}
//
//
//
if

Pour renvoyer le nombre dlments supprims, il faut indiquer une


clause where. Pour supprimer toutes les lignes et renvoyer une valeur,
on passe "1".
(selection == null)
selection = 1;

// Excute la suppression.
int deleteCount = db.delete(MySQLiteOpenHelper.DATABASE_TABLE, selection,
selectionArgs);
// Prvient les observateurs de la modification de lensemble des donnes.
getContext().getContentResolver().notifyChange(uri, null);
return deleteCount;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// Ouvre une base en lecture/criture pour la transaction.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();
// Pour ajouter des lignes vides la base de donnes en passant un
// ContentValues vide, il faut utiliser le paramtre de colonne null pour
// indiquer le nom de la colonne qui peut tre initialise null.
String nullColumnHack = null;
// Insre les valeurs dans la table.
long id = db.insert(MySQLiteOpenHelper.DATABASE_TABLE,
nullColumnHack, values);

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 300

03/08/12 07:26

Chapitre 8

Bases de donnes et fournisseurs de contenu

301

if (id > -1) {


// Construit et renvoie lURI vers la ligne venant dtre insre.
Uri insertedId = ContentUris.withAppendedId(CONTENT_URI, id);
// Prvient les observateurs de la modification de lensemble des donnes.
getContext().getContentResolver().notifyChange(insertedId, null);
return insertedId;
} else
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// Ouvre une base en lecture/criture pour la transaction.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();
// Si cest une URI de ligne, limite la mise jour la ligne indique.
switch (uriMatcher.match(uri)) {
case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
selection = KEY_ID + = + rowID
+ (!TextUtils.isEmpty(selection) ?
AND ( + selection + ) : );
default: break;
}
// Effectue la mise jour.
int updateCount = db.update(MySQLiteOpenHelper.DATABASE_TABLE,
values, selection, selectionArgs);
// Prvient les observateurs de la modification de lensemble des donnes.
getContext().getContentResolver().notifyChange(uri, null);
return updateCount;
}

10. Ceci termine la classe du fournisseur de contenu. Ajoutez-la au manifeste de


votre application, en indiquant lURI qui servira dautorit.
<provider android:name=.ToDoContentProvider
android:authorities=com.paad.todoprovider/>

11. Revenez lactivit ToDoList et modifiez-la pour rendre persistant le tableau de la


liste de tches. Commencez par modifier lactivit pour quelle implmente Loade
rManager.LoaderCallbacks<Cursor>, puis ajoutez les bauches de mthodes
associes.
public class ToDoList extends Activity implements
NewItemFragment.OnNewItemAddedListener, LoaderManager.
LoaderCallbacks<Cursor> {
// [... Code existant de lactivit ToDoList ...]

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 301

03/08/12 07:26

302

Android 4

public Loader<Cursor> onCreateLoader(int id, Bundle args) {


return null;
}
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
}
public void onLoaderReset(Loader<Cursor> loader) {
}
}

12. Compltez le gestionnaire onCreateLoader pour quil construise et renvoie un


objet CursorLoader qui demande tous les lments de ToDoListContentProvider.
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
CursorLoader loader = new CursorLoader(this,
ToDoContentProvider.CONTENT_URI, null, null, null, null);
return loader;
}

13. Lorsque la requte de loader se termine, le curseur rsultat est renvoy au


gestionnaire onLoadFinished. Modifiez ce dernier pour quil parcoure le curseur
et remplisse lArrayAdapter de la liste de tches en consquence.
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
int keyTaskIndex = cursor.getColumnIndexOrThrow(ToDoContentProvider.
KEY_TASK);
todoItems.clear();
while (cursor.moveToNext()) {
ToDoItem newItem = new ToDoItem(cursor.getString(keyTaskIndex));
todoItems.add(newItem);
}
aa.notifyDataSetChanged();
}

14. Modifiez le gestionnaire onCreate pour lancer le chargeur la cration de


lactivit et le gestionnaire onResume pour relancer le chargeur lorsque lactivit
est redmarre.
public void onCreate(Bundle savedInstanceState) {
// [... Code existant de onCreate ...]
getLoaderManager().initLoader(0, null, this);
}
@Override
protected void onResume() {
super.onResume();
getLoaderManager().restartLoader(0, null, this);
}

2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 302

03/08/12 07:26

Chapitre 8

Bases de donnes et fournisseurs de contenu

303

15. La dernire tape consiste modifier le comportement du gestionnaire


onNewItemAdded. Au lieu dajouter directement llment la liste de tches,
utilisez le rsolveur de contenu pour lajouter au fournisseur de contenu.
public void onNewItemAdded(String newItem) {
ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
values.put(ToDoContentProvider.KEY_TASK, newItem);
cr.insert(ToDoContentProvider.CONTENT_URI, values);
getLoaderManager().restartLoader(0, null, this);
}

Info
Tous les extraits de code de cet exemple font partie du projet Todo List Chapitre 8,
disponible sur le site consacr cet ouvrage.

Vous avez cr une base de donnes dans laquelle sauvegarder vos tches. Une
meilleure approche que la copie des lignes du curseur dans une ArrayList consiste
utiliser un SimpleCursorAdapter. Nous le ferons plus loin dans ce chapitre, dans
la section "Crer un fournisseur de tremblements de terre avec la fonctionnalit de
recherche".
Pour rendre cette application de liste de tches plus utile, vous pourriez ajouter la
possibilit de supprimer, modifier les tches de la liste, modifier lordre du tri et vous
pourriez stocker des informations supplmentaires.

Ajouter une fonctionnalit de recherche vos applications


Permettre deffectuer des recherches dans le contenu de vos applications est un
moyen simple et puissant damliorer limplication de lutilisateur et la visibilit dune
application. Sur les terminaux mobiles o la rapidit est le critre prdominant, la
recherche offre un mcanisme permettant aux utilisateurs de trouver rapidement les
informations dont ils ont besoin.
Android fournit un framework qui simplifie la recherche dinformations dans les
fournisseurs de contenu en ajoutant vos activits une fonction de recherche et en
intgrant les rsultats de cette recherche lcran daccueil.
Jusqu Android 3.0 (API level 11), la plupart des terminaux Android disposaient dune
touche recherche physique. Sur les versions plus rcentes, celle-ci a t remplace
par des widgets qui sont gnralement placs dans la barre daction de lapplication.
En implmentant la recherche dans votre application, vous pouvez prsenter une
fonctionnalit de recherche spcifique votre application chaque appui sur le
bouton ou le widget de recherche.
2012 Pearson France Android 4 Reto Meier

Livre ANDROID4.indb 303

03/08/12 07:26

You might also like