• 20 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 15/04/2019

Manipulez vos données grâce aux DAO

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Maintenant que nos entités sont définies, il serait intéressant de pouvoir les manipuler à travers notre base de données SQLite. Par exemple, nous aimerions pouvoir ajouter une chose à faire dans notre base, la mettre à jour ou encore la supprimer... bref, réaliser les différentes actions CRUD !

Et bien c'est ce que nous allons faire immédiatement grâce à un patron de conception (ou Design Pattern en anglais) appelé Data Access Object (DAO), en français, un Objet d'Accès aux Données.

Euh attends, c'est quoi un design pattern ? Pourquoi tu nous parles de DAO tout d'un coup ? :'(

Bonne question ! Un Design Pattern est un modèle ou un ensemble de bonnes pratiques logicielles destiné à aider les développeurs à concevoir la meilleure solution face à un même problème logiciel. On ne réinvente jamais la roue !

Dans notre cas, nous avons un problème : quelle est la meilleure façon d'accéder aux données se trouvant dans notre base de données SQLite ? Autrement dit, l'approche la plus "propre" en programmation orientée objet ?

                                                             

Eh bien c'est là que le DAO apparaît ! :D Ce pattern nous propose de regrouper les accès aux données persistantes dans des classes à part, plutôt que de les disperser. Et si on appliquait cela à nos deux tables  User  et  Item  ?

Ainsi, créez un package appelé database/ puis un sous package dao/ et placez-y les deux interfaces (attention, pas des classes hein... ;)) suivantes :  ItemDao  et  userDao .

Interface database/dao/ItemDao.java :

@Dao
public interface ItemDao {

    @Query("SELECT * FROM Item WHERE userId = :userId")
    LiveData<List<Item>> getItems(long userId);

    @Insert
    long insertItem(Item item);

    @Update
    int updateItem(Item item);

    @Query("DELETE FROM Item WHERE id = :itemId")
    int deleteItem(long itemId);
}

Explications : Room nous impose un format particulier pour créer un DAO en nous demandant de créer une interface par DAO. Ici, nous avons créé l'interface  ItemDao  dont l'objectif et la responsabilité seront de regrouper toutes les actions CRUD pour la table Item

Afin d'indiquer l'interface comme étant une classe DAO, nous avons ajouté l'annotation @Dao en haut de celle-ci. Puis nous avons créé les 4 actions CRUD via les méthodes suivantes :

  • getItems() : Permet de récupérer la liste des choses à faire (Item) pour un utilisateur. Nous avons utilisé l'annotation @Query permettant de définir la méthode comme étant une requête SQL. Nous retournons une liste d'Item de type LiveData, que j'expliquerai en détail dans la prochaine partie de ce cours... :) 

  • insertItem() : Permet de créer une nouvelle chose à faire (Item) grâce à l'annotation@Insert. Notez que nous lui avons passé un objet Item directement en paramètre de la méthode. Cet objet n'aura pas besoin d'avoir un identifiant de défini, car rappelez-vous du précédent chapitre, Room le génèrera pour nous !

  • updateItem() : Permet de mettre à jour une chose à faire existante (Item) grâce à l'annotation  @Update. Notez également que nous lui avons passé un objet Item directement en paramètre. Cet objet devra absolument avoir un identifiant défini, afin que Room puisse le retrouver dans la BDD et le mettre à jour... ;)

  • deleteItem() : Permet de supprimer une chose à faire existante (Item) en BDD. Notez ici que nous avons ré-utilisé l'annotation  @Query  car nous avions besoin de créer une requête SQL un peu poussée. Vous avez également la possibilité de passer directement un objet Item et d'utiliser l'annotation  @Delete  pour le supprimer.

Interface database/dao/UserDao.java :

@Dao
public interface UserDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void createUser(User user);

    @Query("SELECT * FROM User WHERE id = :userId")
    LiveData<User> getUser(long userId);
}

Explications : Nous avons ici défini l'interface DAO dédiée à la manipulation de la table  User . Nous avons ajouté seulement deux méthodes : Une pour créer un nouvel utilisateur (  createUser ) et une pour récupérer un utilisateur ( getUser ). Vous remarquerez que nous avons ajouté à l'annotation  @Insert , le paramètre  onConflict = OnConflictStrategy.REPLACE  permettant d'écraser un utilisateur déjà existant possédant le même ID que celui que l'on souhaite insérer.

Et c'est tout ! :D Enfin si, il nous reste tout de même à configurer une classe très importante, dont le rôle sera de lier toutes les classes/interfaces que nous avons précédemment créées ensemble, et surtout de configurer notre base de données !

Ainsi, je vous laisse créer la classe SaveMyTripDatabase dans le package database/.

Classe database/SaveMyTripDatabase.java :

@Database(entities = {Item.class, User.class}, version = 1, exportSchema = false)
public abstract class SaveMyTripDatabase extends RoomDatabase {

    // --- SINGLETON ---
    private static volatile SaveMyTripDatabase INSTANCE;

    // --- DAO ---
    public abstract ItemDao itemDao();
    public abstract UserDao userDao();

    // --- INSTANCE ---
    public static SaveMyTripDatabase getInstance(Context context) {
        if (INSTANCE == null) {
            synchronized (SaveMyTripDatabase.class) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                            SaveMyTripDatabase.class, "MyDatabase.db")
                            .addCallback(prepopulateDatabase())
                            .build();
                }
            }
        }
        return INSTANCE;
    }

    // ---

    private static Callback prepopulateDatabase(){
        return new Callback() {

            @Override
            public void onCreate(@NonNull SupportSQLiteDatabase db) {
                super.onCreate(db);

                ContentValues contentValues = new ContentValues();
                contentValues.put("id", 1);
                contentValues.put("username", "Philippe");
                contentValues.put("urlPicture", "https://oc-user.imgix.net/users/avatars/15175844164713_frame_523.jpg?auto=compress,format&q=80&h=100&dpr=2");

                db.insert("User", OnConflictStrategy.IGNORE, contentValues);
            }
        };
    }
}

Explications : Nous créons ici une classe abstraite, héritant de  RoomDatabase  et étant définie par l'annotation  @Database . Cette même annotation répertoriera les différentes tables (appelées "entities").

A l'intérieur de cette classe, nous avons déclaré nos deux interfaces de DAO. Puis, nous avons créé une méthode,  getInstance() , permettant de créer un singleton de notre classe SaveMyTripDatabase.

Euuuuuh, c'est quoi un Singleton ? :o

Eh bien c'est encore un design pattern... ;) En fait, nous rencontrons ici malgré nous un problème logiciel : comment créer UNE SEULE FOIS la classe responsable de notre base de données et n'obtenir qu'une seule et unique instance de référence ? 

Ah. Mais pourquoi a-t-on besoin d'une seule instance de notre classe gérant notre base de données ?

Tout simplement car si vous avez plusieurs instances de votre base de données, vous autorisez potentiellement plusieurs instances à manipuler l'unique fichier de votre base en même temps ! Si deux instances ouvrent le fichier de SQLite, et qu'elles tentent de le modifier en même temps, cela risque de poser un léger problème, non ? ;) 

C'est donc pour cette raison que nous créons un Singleton ! Ce dernier créera un nouvel objet RoomDatabase grâce à son builder Room.databaseBuilder et créera un fichier qui contiendra notre base de données SQLite. Si jamais cette méthode est rappelée par la suite, nous renverrons uniquement la référence de notre base de données.

Aussi, comme vous pouvez le voir, nous avons rajouté la méthode  addCallback  à notre builder, qui nous permettra de remplir celle-ci avec un utilisateur de test grâce à la méthode que nous avons créée juste en dessous, prepopulateDatabase .

Et voilà ! Notre base de données SQLite est correctement configurée grâce à Room. Il ne nous reste plus qu'à essayer tout cela... Et c'est que nous allons faire dans le prochain chapitre ! ;)

Exemple de certificat de réussite
Exemple de certificat de réussite