IUT de Lannion Dept Informatique 1. Programmation Android TP6 - Bases de données P. Nerzic 2016-17 Base de données SQLite Dans ce TP, nous allons voir comment créer et utiliser une base de données SQL locale pour stocker les informations. La semaine prochaine, ça sera avec un WebService. On repart de l’application AvosAvis de la semaine précédente. . . quelque soit son état d’avancement. Vous pouvez même repartir de la version de base, AvosAvis.zip, mais évidemment la vôtre1 sera plus complète. Faites une copie du projet AvosAvis et appelez-la AvosAvisSQL, puis travaillez uniquement sur cette copie. 1.1. Essais en ligne de commande (optionnels) Lancez un AVD (pas la tablette réelle, car sqlite3 n’y est pas installé en tant que programme utilisateur). Relisez le TP1 en ce qui concerne la connexion en shell avec la commande adb. Connectez-vous en shell sur l’AVD. NB: adb adb adb il peut y avoir un bug. Si vous n’arrivez pas à vous connecter à la tablette, alors tapez : kill-server start-server devices Dans le shell de la tablette, lancez la commande sqlite3 sans paramètre. En faisant ainsi, ce qu’on va créer ne sera qu’en mémoire vive, pas enregistré dans un fichier. Faites-vous la main avec quelques requêtes SQL et directives spécifiques de SQLite : CREATE TABLE Loisirs ( _id INTEGER PRIMARY KEY AUTOINCREMENT, nom TEXT NOT NULL); .schema Loisirs INSERT INTO Loisirs VALUES (1,'sport'); INSERT INTO Loisirs VALUES (2,'informatique'); INSERT INTO Loisirs VALUES (null,'cinéma'); INSERT INTO Loisirs VALUES (null,'santé'); .dump Loisirs SELECT * FROM Loisirs; .headers on SELECT nom FROM Loisirs WHERE _id=3; .dump Loisirs UPDATE Loisirs SET nom='ordinateur' WHERE _id=2; DELETE FROM Loisirs WHERE nom='santé'; SELECT * FROM Loisirs ORDER BY nom ASC; .exit Notez que l’identifiant d’une table doit TOUJOURS être un entier appelé _id. Si on fournit null 1 Ne copiez pas le projet d’un autre étudiant, ce serait très peu apprécié. Demandez plutôt de l’aide. Le but, c’est que vous appreniez quelque chose, pas que le TP semble fini. 1 IUT de Lannion Dept Informatique Programmation Android TP6 - Bases de données P. Nerzic 2016-17 en tant qu’identifiant alors SQLite en attribue un automatiquement. C’est le rôle de l’option AUTOINCREMENT (SERIAL dans PostgreSQL). 1.2. Table Avis On va commencer par définir une classe TableAvis qui représente la table Avis (libellé, note, auteur et date). Relisez le cours de la semaine 6, notamment le transparent intitulé « Classe pour une table ». L’idée, c’est que cette classe va remplacer le ArrayList des TPs précédents. Complétez les méthodes de la classe TableAvis (cherchez les TODO) : • void create(SQLiteDatabase bdd) Cette méthode effectue la création de la table dans la base de données. Elle exécute une requête CREATE TABLE Avis... à l’aide de la méthode execSQL sur le paramètre bdd. Les attributs de la table sont : – _id de type INTEGER PRIMARY KEY AUTOINCREMENT – libelle de type TEXT NOT NULL – note de type REAL – auteur de type TEXT – date de type INTEGER. Remarque : le type LONG ou TIMESTAMP n’existe pas. • void drop(SQLiteDatabase bdd) Cette méthode supprime totalement la table Avis de la base. Utiliser la méthode execSQL. NB: utilisez l’option IF EXISTS afin de parer aux erreurs de double suppression. • Long insertAvis(SQLiteDatabase bdd, Avis avis) Cette méthode ajoute l’avis indiqué à la table Avis. Vous avez deux manières de le faire. Soit utiliser la méthode execSQL, soit utiliser la méthode insert. La seconde est à préférer car elle retourne l’identifiant du nouveau n-uplet. Il pourrait être repris pour créer une relation, en tant que clé étrangère dans une autre table. La méthode insert est plus compliquée que execSQL car il faut créer un ContentValues et mettre les colonnes dedans. Relisez le cours, transparents « Exemples de méthodes » et « Méthode insert ». Regardez aussi les accesseurs de la classe Avis. En ce qui concerne la date, on vous suggère d’utiliser la méthode getTimestamp de l’avis. Le principe est de transformer la date en un nombre de millisecondes écoulées depuis le 01/01/1970. Ça s’appelle un timestamp et si vous devez travailler avec des dates, vous passerez beaucoup de temps à convertir des timestamps en date et vice-versa. Beaucoup de SGBD possèdent les types DATE, TIME, DATETIME et TIMESTAMP, mais pas sqlite3. • void updateAvis(SQLiteDatabase bdd, Long id, Avis avis) Cette méthode modifie le n-uplet identifié par id. En fait, c’est presque la même méthode que la précédente, mais on fait UPDATE au lieu de INSERT et il faut fournir une clause de sélection. Vous pouvez également soit utiliser execSQL, soit update. Relisez le cours. Si vous utilisez update, n’oubliez pas de convertir l’identifiant en chaîne. • void deleteAvis(SQLiteDatabase bdd, Long id) Cette méthode supprime le n-uplet identifié par id. 2 IUT de Lannion Dept Informatique Programmation Android TP6 - Bases de données P. Nerzic 2016-17 • Avis getAvis(SQLiteDatabase bdd, Long id) Cette méthode retourne un nouvel objet Avis construit à partir du n-uplet identifié par id. Elle crée un curseur, effectue une requête par rawQuery et extrait les colonnes qu’elle place dans un nouvel avis retourné en résultat, puis elle ferme le curseur. La création d’un Avis à partir d’un curseur se fait avec une méthode statique de la classe TableAvis appelée fromCursor qu’on vous invite à aller voir. 1.3. Helper pour gérer la base de données L’étape suivante consiste à créer un helper pour gérer la base de données. Vous avez juste à compléter celui qui est fourni, comme dans le cours : rajoutez-lui l’appel qui crée la table Avis et celui qui supprime la table. Vous comprenez que dans une application ayant plusieurs tables, il faudrait mettre tous les appels aux fonctions de création nécessaires et dans le bon ordre pour respecter les contraintes de clés étrangères. 1.4. Nettoyage Avant d’attaquer la suite, vous allez supprimer la classe AvosAvisApplication et enlever l’attribut android:name de l’application dans le manifeste. Cela va causer un grand nombre d’erreurs dans MainActivity et EditActivity : chaque fois que vous utilisez encore l’ancienne liste. On va les supprimer peu à peu. 1.5. MainActivity Maintenant, l’objectif est de supprimer partout le ArrayList global de l’application et de mettre des appels aux méthodes de TableAvis à la place. Il y a quelques transformations pour remplacer le ArrayList par la base SQL. • Variables membres : – Rajoutez deux variables membres : MySQLiteHelper helper; SQLiteDatabase bdd; – Changer le type de la variable membre adapter en CursorAdapter. Ça va entraîner de nouvelles erreurs tant que tout n’est pas cohérent. • Méthode onCreate, devinez ce qu’il faut faire des deux membres helper et bdd. La réponse est donnée dans le cours, transparent intitulé « Utilisation du Helper dans l’application ». La base doit être ouverte en écriture si vous avez mis en place le menu contextuel, sinon en lecture quand l’activité ne modifie jamais les données. • Complétez la méthode onDestroy de l’activité en rajoutant la fermeture du helper. • Méthode onCreate encore, vous pourrez tester deux adaptateurs différents (un seul à la fois) : – Le type SimpleCursorAdapter : adapter = new SimpleCursorAdapter( this, 3 Programmation Android TP6 - Bases de données IUT de Lannion Dept Informatique P. Nerzic 2016-17 android.R.layout.simple_list_item_2, null, new String[]{TableAvis.Libelle, TableAvis.Note}, new int[]{android.R.id.text1, android.R.id.text2}, 0); C’est un adaptateur qui affiche les champs dans un layout standard d’Android. Il ne peut afficher que deux chaînes, un titre et un sous-titre. La note est automatiquement convertie en texte. – Le type AvisCursorAdapter : adapter = new AvisCursorAdapter(this, null, R.layout.item_avis); C’est un adaptateur dérivé de CursorAdapter similaire au AvisAdapter du TP4. • Toujours dans onCreate, il faut créer un chargeur de curseur et le démarrer. Ça se fait avec cette ligne à la fin de la méthode, après avoir ouvert la base de données et créé l’adapter : getLoaderManager().initLoader(AVIS_LOADER_ID, null, this); Vous allez avoir une erreur quand vous ferez ça, car il faudrait que this implémente une interface. Allez voir dans le cours le transparent intitulé « Activité ou fragment d’affichage d’une liste ». Les méthodes de cette interface sont déjà programmées, vous devez seulement rajouter l’interface nécessaire à la liste implements. Attention, AndroidStudio vous propose deux packages possibles pour importer Loader et LoaderManager, il ne faut pas prendre ceux concernant appcompat, mais android.app.LoaderManager et android.content.Loader. • Mise à jour de la liste : à la place des adapter.notifyDataSetChanged(), mettre ce qui suit. Si c’est dans un écouteur, alors vous devez mettre le nom de la classe englobante devant this : getLoaderManager().restartLoader(AVIS_LOADER_ID, null, this); • Repérez toutes les lignes marquées TODO et faites ce qui est indiqué : décommenter la ligne suivante et parfois supprimer la suivante. Pour toutes les méthodes surchargées, vous pouvez décommenter l’annotation @Override. 1.6. EditActivity Comme dans MainActivity, il faut remplacer les actions sur le ArrayList global par des requêtes SQL à travers la classe TableAvis. • Variables membres : – Rajoutez deux variables membres : MySQLiteHelper helper; SQLiteDatabase bdd; et recopiez les mêmes instructions pour ouvrir la base, cette fois toujours en écriture. • Complétez la méthode onDestroy en rajoutant la fermeture du helper. 4 IUT de Lannion Dept Informatique Programmation Android TP6 - Bases de données P. Nerzic 2016-17 • Il faut maintenant remplacer les accès au ArrayList par un accès à la base. Par exemple, dans la méthode display : // récupérer l'item désigné par l'identifiant Avis avis = TableAvis.getAvis(bdd, identifiant); C’est la méthode valider qui vous posera le plus de problèmes pour comprendre exactement ce qu’il faut faire, dans quel ordre et ce qui change par rapport à la liste. 1.7. Réflexion Un problème de réflexion pour finir : comment pourrait-on organiser les classes s’il y avait une deuxième table dans la base ? Actuellement, il n’y a que la table Avis, mais si on rajoute une table Categories contenant des catégories d’avis comme repas, sorties, sports, études. . . ; cette catégorie étant une clé étrangère dans la table Avis. Il faudrait créer une classe TableCategories avec ses requêtes comme dans TableAvis. Cela paraît assez simple si c’est une relation 1-n (un avis a une et une seule catégorie, mais une catégorie peut concerner plusieurs avis). Lors de la création de la base de données, il faut appeler la méthode de création des deux tables dans le bon ordre. Ensuite, l’un des traitements est l’ajout d’un avis ayant une nouvelle catégorie, il faut insérer la catégorie, récupérer son identifiant puis insérer l’avis. À moins d’avoir une activité spécifiquement pour gérer les catégories et qu’on ne puisse pas créer de catégorie dans un avis mais seulement choisir parmi les existantes. Mais si la relation est du type n-n (un avis peut avoir plusieurs catégories et une catégorie peut concerner plusieurs avis), représentée par une autre table (CategoriesAvis), comment pourrait-on organiser les classes et quelles méthodes elles pourraient contenir (question ouverte) ? 2. Travail à rendre Avec le navigateur de fichiers, descendez dans le dossier app/src du projet AvosAvis du TP6. Rajoutez un fichier appelé exactement IMPORTANT.txt dans le sous-dossier main si vous avez rencontré des problèmes techniques durant le TP : plantages, erreurs inexplicables, perte du travail, etc. mais pas les problèmes dus à un manque de travail ou de compréhension. Décrivez exactement ce qui s’est passé. Le correcteur pourra lire ce fichier au moment de la notation et compenser votre note. Cliquez droit sur le dossier main, choisissez Compresser..., format tar.gz, cliquez sur Creer. Déposez l’archive main.tar.gz sur Moodle au bon endroit. 5