Premier pas sur SugarORM

 

Tout ceux qui se seront frottés aux bases de données sous Android savent que c'est une vraie plaie. Pour chaque table il faut créer 3 classes (DAO, Contract et Helper), dès qu'on change quelque chose à la structure il faut flinguer toute la table, bref il y a de quoi retourner en courant chez MySQL.

Les ORM (object-relational mapping) proposent de simplifier tout cela. Le but d'un ORM est de définir des correspondances entre les schémas de la base de données et les classes de l'application. ORMLite, le pionnier a la réputation d'être un peu lourd. J'ai donc tenté SugarORM (à ne pas confondre avec SugarCRM), un ORM signé Satya Narayan, qui propose non seulement de nous débarrer de l'obligation de créer toutes ces classes à la con, mais en plus de nous éviter d'avoir à écrire la moindre requête SQLLite (perso j'ai 15 ans de MySQL derrière moi, donc ce n'est pas une petite requête qui va me faire peur, mais bon hein, ça peut faire plaisir à certains…)

Par contre, pas de miracle :

  • si on rajoute un attribut dans une des classes POJO. SugarORM est infichu de rajouter la colonne dans la table correspondante, donc il faut suivre cette procédure
  • Sugar ORM ne gère pas non plus les relations 1 à N. Donc, si vous avez des ArrayLists dans votre classe POJO, vous aurez un petit message d'erreur (heurement non bloquant)

Attention, le Getting Started date de la version 1.3. Pour avoir celui de la tout dernière version, il vaut mieux aller sur la page du dépôt Git. Et ne rêvez pas, c'est aussi peu détaillé !

Découvrons de ce pas cet ORM…

Importation de la bibliothèque

On va considérer que vous utilisez Gradle. Au passage, il faudra expliquer au concepteur qu'on n'utilise plus désormais plus compile mais implementationDans le build.gradle du dossier app/ de l'application, ajouter :

implementation compile 'com.github.satyan:sugar:1.5'

Puis synchroniser, comme le demande Android Studio

 

Dans le manifest de l'application

Dans app/src/main/AndroidManifest.xml, ajouter entre les balises <application> et </application>

<meta-data android:name="DATABASE" android:value="mabase.db" />
<meta-data android:name="VERSION" android:value="1" />
<meta-data android:name="QUERY_LOG" android:value="true" />
<meta-data android:name="DOMAIN_PACKAGE_NAME" android:value="com.kdjwebdesign.monapp" />

(pensez à adapter avec vos propres nom de base et package name d'application)

Et comme attribut dans la balise <application> elle-même

android:name="com.orm.SugarApp"

… et penser à supprimer android:icon= »@mipmap/ic_launcher » sinon vous aurez cette erreur à la compilation

Manifest merger failed : Attribute application@icon value=(@mipmap/ic_launcher) from AndroidManifest.xml:21:9-43
is also present at [com.github.satyan:sugar:1.3] AndroidManifest.xml:13:9-45 value=(@drawable/ic_launcher).
Suggestion: add ‘tools:replace= »android:icon »‘ to <application> element at AndroidManifest.xml:18:5-67:19 to override.

 

Classes POJO

Petit rappel, une classe POJO (Plain old Java object) est une classe toute simple avec juste un constructeur et des attributs, et qui ici va servir de template pour créer la table correspondante. Je vais reprendre l'exemple du tuto officiel, avec la classe POJO Book. Attention, il y a eu des changements entre la version 1.3 et la version  1.5 (autrefois, on devait hériter de SugarRecord<Book>)

Attention aussi à l'annotation @Unique ! Voir plus bas…

public class Book extends SugarRecord {
  @Unique
  String isbn;
  String title;
  String edition;

  // Default constructor is necessary for SugarRecord
  public Book() {

  }

  public Book(String isbn, String title, String edition) {
    this.isbn = isbn;
    this.title = title;
    this.edition = edition;
    }
}

 

L'annotation @Unique

Rien n'étant expliqué à son sujet, j'ai cru qu'il fallait la mettre dans chaque classe POJO. Grave erreur… du coup, chaque enregistrement ajouté supprimait le précédent. Des heures de perdues (en plus je croyais que c'était un souci lié à l'ajout d'une nouvelle colonne dans une des tables. Pfff)

Erreurs rencontrées

Attention, si vos classes POJO ont déjà un champ id, ça va coincer. Vous allez rencontrer les erreurs

class com.kdjwebdesign.monapp.model.Chat declares multiple JSON fields named id

et

error: id has protected access in SugarRecord

 

Si vous mettez vos balises <meta-data> avant la balise <application> dans le manifest, l'application va crasher avec ce message d'erreur surréaliste

java.lang.NoSuchMethodException: addFontWeightStyle

 

Gestion des dates

Les dates sont stockées par Sugar ORM sous la forme d'un timestamp, donc une valeur numérique. Mais dans ma classe POJO, j'utilisais un objet java.util.Date… Or, avec la version 1.3 de Sugar CRM, on obtient ce message d'erreur au moment d'appeler la méthode save() pour insérer ou mettre à jour un enregistrement.

java.lang.NullPointerException: Attempt to invoke virtual method ‘long java.util.Date.getTime()' on a null object reference

On retrouve le souci dans ce ticket de 2015. Dans lequel le développeur affirme avoir résolu cela dans la version 1.4. C'est là que j'ai réalisé que la 1.3 n'était pas la dernière version… Mais bordel, mettez à jour vos exemples un minimum !

 

Comment récupérer l'id de l'enregistrement

On a vu qu'il fallait supprimer les attributs id de nos classes POJO. Sauf qu'on va avoir besoin de récupérer l'id de l'enregistrement à chaque fois qu'on va vouloir y accéder ! Comme pour la majorité des frameworks et bibliothèques, l'utilisateur est laissé en plan dès qu'il veut faire quelque chose d'un peu élaboré…

Il n'y a plus qu'à chercher dans des pages de documentation (ah non, zut, c'est vrai, elle est introuvable) ou d'espérer que quelqu'un d'autre a eu le même souci sur Stackoverflow

Là encore, je me heurte aux changements de nommage entre les versions de SugarORM : la méthode save() ne renvoyait pas long mais void dans la version 1.3, il n'y a pas de méthode getid()… elle s'appelle désormais getId() ! Grrrr…

Et puis, les id dans SugarORM sont au format long ! Comme j'utilisais des int pour passer mes id en Extra d'une activité à l'autre, il a fallu repasser partout pour transformer ça en long, sinon bonjour les erreurs de cast…

private long id;

id = intent.getLongExtra("idAppli",0);

Quelques exemples

Récupérer tous les enregistrements d'une table classés

J'aimerais récupérer une liste classée par nom. Pour cela, je vais utiliser la méthode listAll en lui passant le nom de la colonne de tri en 2e paramètre.

listeBooks = Book.listAll(Book.class, "title");

Et si je veux les enregsitrements les plus récents en premiers :

listeBooks = Book.listAll(Book.class, "date_release DESC");

Récupérer des enregistrements qui satisfont à une condition

Un enregistrement d'après son id

Book b=Book.findById(Book.class, idBook);

Une liste d'enregistrements d'après un critère (WHERE)

List<Book> booksList = Select.from(Book.class).where(Condition.prop("id_editeur").eq(idEditeur)).list();

Ajout d'une nouvelle colonne

Alors là, bienvenue en enfer !

Créer un répertoire nommé /sugar_upgrades dans le répertoire /assets de votre projet (si vous en avez déjà un, sinon il se crée dans app/src/main/)

A l'intérieur de /sugar_upgrades, créer un fichier nommé <version>.sql, dont le numéro de version correspond à celui de la base. Par exemple 1.sql, 2.sql… Ce fichier contient le script SQL correspondant à l'évolution de la base. Par exemple

alter table NOTE add NAME TEXT;

Adapter ce numéro de version à celui de la balise <meta-data android:name= »VERSION »> dans le manifest

Bref, c'est la grosse galère, et il vaut mieux avoir bien réfléchi à la structure de sa base de données… Et si cela ne fonctionne toujours pas, je vous invite à consulter ce ticket

 

En conclusion, oui SugarORM facilite bien la tâche, mais non, on ne peut pas encore parler de « insanely easy way to work with Android databases »

Karine SANCHE

Partager cet article