• Aucun résultat trouvé

Création d’une base de données et d’un fournisseur de contenu pour la liste de tâches

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

ToDoContentProvider

qui é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

,

delete

et

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

MySQLiteOpenHelper

pour 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

onCreate

et

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 à

ToDoContentProvider

et ajoutez-lui une variable privée pour stocker une instance de la classe

MySQLiteOpenHelper

que 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

UriMatcher

pour

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

getType

pour 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

,

insert

et

update

selon 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é

ToDoList

et 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

onCreateLoader

pour qu’il construise et renvoie un objet

CursorLoader

qui 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

loader

se termine, le curseur résultat est renvoyé au gestionnaire

onLoadFinished

. Modifiez ce dernier pour qu’il parcoure le curseur et remplisse l’

ArrayAdapter

de 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

onCreate

pour lancer le chargeur à la création de l’activité et le gestionnaire

onResume

pour 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.

Vous avez créé une base de données dans laquelle sauvegarder vos tâches. 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 "Créer un fournisseur de tremblements de terre avec la fonctionnalité de recherche".

Pour rendre cette application de liste de tâches plus utile, vous pourriez ajouter la possibilité de supprimer, modifier les tâches de la liste, modifier l’ordre du tri et vous pourriez stocker des informations supplémentaires.

Ajouter une fonctionnalité de recherche à vos applications

Documents relatifs