Au Chapitre 4, vous avez créé une application pour gérer une liste de tâches. Nous
allons ici créer une base de données et un fournisseur de contenu pour sauvegarder
chaque élément de la liste.
1. Commencez par créer une classe
ToDoContentProviderqui étend
ContentProvider
. Elle servira à héberger la base de données en utilisant un
SQLiteOpenHelper
et à gérer les interactions de la base. Ajoutez-lui des ébauches pour les méthodes
onCreate,
query,
update,
insert,
deleteet
getType, ainsi qu’un squelette d’implémentation d’un
SQLiteOpenHelper:
package com.paad.todolist;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.text.TextUtils;
import 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;
}
private static class MySQLiteOpenHelper extends SQLiteOpenHelper { public MySQLiteOpenHelper(Context context, String name,
CursorFactory factory, int version) { super(context, name, factory, version);
}
// Appelée lorsque aucune base n’existe sur le disque et que la classe // helper a besoin d’en créer une nouvelle.
@Override
public void onCreate(SQLiteDatabase db) { // À faire : créer les tables de la base.
}
// Appelée s’il y a un conflit de version de la base, ce qui signifie // que la version de la base de données 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 l’URI de ce fournisseur. Cette URI servira à accéder à ce fournisseur de contenu depuis d’autres composants d’application via le résolveur de contenu.
public static final Uri CONTENT_URI =
Uri.parse(“content://com.paad.todoprovider/todoitems”);
3. Créez des variables statiques publiques qui définissent les noms des colonnes.
Elles serviront à l’objet
MySQLiteOpenHelperpour créer la base et aux autres composants d’application pour extraire des valeurs de vos requêtes.
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, créez des variables pour stocker le nom et la version de la base de données ainsi que le nom de la table de la liste de tâches.
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, récrivez les méthodes
onCreateet
onUpgrade
pour traiter la création de la base de données en utilisant les colonnes de l’étape 3 et des instructions de mise à jour standard.
// Instruction SQL pour créer une base de données. .
private static final String DATABASE_CREATE = “create table “ + DATABASE_TABLE + “ (“ + KEY_ID +
“ integer primary key autoincrement, “ +
KEY_TASK + “ text not null, “ + KEY_CREATION_DATE + “long);”;
// Appelée lorsque aucune base n’existe sur le disque et que la classe // helper a besoin d’en créer une nouvelle.
@Override
public void onCreate(SQLiteDatabase db) { db.execSQL(DATABASE_CREATE);
}
// Appelée s’il y a un conflit de version de la base, ce qui signifie // que la version de la base de données 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 + “, détruira les anciennes données.”);
// Mise à jour de la base existante pour se conformer à la nouvelle version.
// On peut gérer plusieurs anciennes versions en comparant les valeurs de // oldVersion et newVersion
// Le cas le plus simple consiste à supprimer l’ancienne table et à en créer // une nouvelle.
db.execSQL(“DROP TABLE IF IT EXISTS “ + DATABASE_TABLE);
// Création d’une nouvelle table.
onCreate(db);
}
6. Revenez à
ToDoContentProvideret ajoutez-lui une variable privée pour stocker une instance de la classe
MySQLiteOpenHelperque vous créerez dans le gestion-naire
onCreate.
private MySQLiteOpenHelper myOpenHelper;
@Override
public boolean onCreate() {
// Construit la base de données sous-jacente.
// Reporte l’ouverture de la base tant que l’on n’en a pas besoin pour // une requête ou une transaction.
myOpenHelper = new MySQLiteOpenHelper(getContext(), MySQLiteOpenHelper.DATABASE_NAME, null, MySQLiteOpenHelper.DATABASE_VERSION);
return true;
}
7. Toujours dans le fournisseur de contenu, créez un nouvel objet
UriMatcherpour
permettre à votre fournisseur de contenu de faire la différence entre une requête
sur la table entière et une requête sur une ligne particulière. Utilisez-le dans le
gestionnaire
getTypepour renvoyer le type MIME correct en fonction du type
de la requête.
private static final int ALLROWS = 1;
private static final int SINGLE_ROW = 2;
private static final UriMatcher uriMatcher;
// Remplit l’objet UriMatcher, où une URI se terminant par ’todoitems’
// correspondra à une requête de toutes les tâches et où ’todoitems/[rowID]’
// représente 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 chaîne identifiant le type MIME de l’URI // d’un 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 supportée : “ + uri);
} }
8. Implémentez l’ébauche de la méthode
query. Commencez par demander une instance de la base de données avant de construire une requête utilisant les paramètres passés. Dans cette instance, vous ne devez appliquer les mêmes paramètres de requête qu’à la base de données sous-jacente – ne modifiez la requête que pour prendre en compte la possibilité qu’une URI désigne une seule ligne.
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // Ouvre une base de données en lecture seule.
SQLiteDatabase db = myOpenHelper.getWritableDatabase();
// Remplacer par des instructions SQL valides si nécessaire.
String groupBy = null;
String having = null;
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(MySQLiteOpenHelper.DATABASE_TABLE);
// Si c’est une requête de ligne, limite l’ensemble résultat à la ligne // passée en paramètre.
switch (uriMatcher.match(uri)) { case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
queryBuilder.appendWhere(KEY_ID + “=” + rowID);
default: break;
}
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, groupBy, having, sortOrder);
return cursor;
}
9. Implémentez les méthodes
delete,
insertet
updateselon la même approche – passez les paramètres reçus 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 c’est une URI de ligne, limite la suppression à la ligne indiquée.
switch (uriMatcher.match(uri)) { case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
selection = KEY_ID + “=” + rowID
+ (!TextUtils.isEmpty(selection) ? “ AND (“ + selection + ’)’ : “”);
default: break;
}
// Pour renvoyer le nombre d’éléments supprimés, il faut indiquer une // clause where. Pour supprimer toutes les lignes et renvoyer une valeur, // on passe "1".
if (selection == null) selection = “1”;
// Exécute la suppression.
int deleteCount = db.delete(MySQLiteOpenHelper.DATABASE_TABLE, selection, selectionArgs);
// Prévient les observateurs de la modification de l’ensemble des données.
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 données en passant un // ContentValues vide, il faut utiliser le paramètre de colonne null pour // indiquer le nom de la colonne qui peut être initialisée à null.
String nullColumnHack = null;
// Insère les valeurs dans la table.
long id = db.insert(MySQLiteOpenHelper.DATABASE_TABLE, nullColumnHack, values);
if (id > -1) {
// Construit et renvoie l’URI vers la ligne venant d’être insérée.
Uri insertedId = ContentUris.withAppendedId(CONTENT_URI, id);
// Prévient les observateurs de la modification de l’ensemble des données.
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 c’est une URI de ligne, limite la mise à jour à la ligne indiquée.
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);
// Prévient les observateurs de la modification de l’ensemble des données.
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 l’URI qui servira d’autorité.
<provider android:name=”.ToDoContentProvider”
android:authorities=”com.paad.todoprovider”/>
11. Revenez à l’activité
ToDoListet modifiez-la pour rendre persistant le tableau de la liste de tâches. Commencez par modifier l’activité pour qu’elle implémente
Loade rManager.LoaderCallbacks<Cursor>, puis ajoutez les ébauches de méthodes associées.
public class ToDoList extends Activity implements NewItemFragment.OnNewItemAddedListener, LoaderManager.
LoaderCallbacks<Cursor> {
// [... Code existant de l’activité ToDoList ...]
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. Complétez le gestionnaire
onCreateLoaderpour qu’il construise et renvoie un objet
CursorLoaderqui demande tous les éléments 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 requête de
loaderse termine, le curseur résultat est renvoyé au gestionnaire
onLoadFinished. Modifiez ce dernier pour qu’il parcoure le curseur et remplisse l’
ArrayAdapterde la liste de tâches en conséquence.
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
onCreatepour lancer le chargeur à la création de l’activité et le gestionnaire
onResumepour relancer le chargeur lorsque l’activité est redémarrée.
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);
}
15. La dernière étape consiste à modifier le comportement du gestionnaire
onNewItemAdded
. Au lieu d’ajouter directement l’élément à la liste de tâches, utilisez le résolveur de contenu pour l’ajouter 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.