• 12 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 26/09/2024

Utilisez l’injection de dépendances et écrivez la couche de données de l’architecture MVVM

Après avoir testé la base de données pour garantir sa fiabilité et son efficacité, le prochain pas naturel pour vous est de structurer votre application en utilisant l’ensemble des bonnes pratiques et des recommandations de Google. Elles concernent l’injection des dépendances pour gérer les liens entre les classes de votre projet, l'utilisation du patron de conception Repository (dépôt en français) pour gérer vos données mais aussi l’utilisation du patron de conception MVVM pour structurer votre application.

Découvrez l’injection de dépendances avec Hilt

Dans une application mobile, les classes ont généralement besoin d'objets d'autres classes pour fonctionner. Lorsqu'une classe nécessite une autre classe, cette dernière est appelée dépendance. Même s’il peut sembler facile d’instancier vous-même les objets requis, cette approche rend le code inflexible et difficile à tester, car la classe et les objets requis sont fortement liés.

Pour rendre le code plus maintenable et évolutif, une solution consiste à ne pas instancier les objets dont dépend une classe dans celle-ci, mais de les instancier en dehors de celle-ci puis de les transmettre. La transmission des objets est alors appelée injection de dépendances ou dependency injection (DI) en anglais.

Dans le cadre du développement Android, il existe plusieurs bibliothèques populaires permettant de mettre en place l’injection de dépendance dont Hilt une bibliothèque conçue par Google. Elle offre une approche simplifiée et efficace pour injecter des dépendances dans les composants Android, tels que les activités, les fragments et les services.

Ajoutez les dépendances à Hilt

Dans le cadre de l’application PETiSoin, c’est donc Hilt qui sera utilisée pour mettre en place l’injection de dépendances dans le projet.

Comme à chaque nouvelle dépendance, la première étape consiste à ajouter celle-ci au sein du catalogue Gradle.

1. Pour ce faire, rendez-vous dans le dossier Gradle du projet.

2. Ouvrez dans Android Studio le fichier "libs.versions.toml".

[versions]
hilt = "2.51.1"

[libraries]
hilt = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" }

[plugins]
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }

3. Déclarez l’usage du plugin dans le projet. Pour ce faire, rendez-vous dans le fichier "build.gradle.kts" qui se trouve à la racine du projet Android Studio pour y ajouter, dans la section plugins, le plugin Hilt :

// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
  //…
  alias(libs.plugins.hilt) apply false
}

4. Activez maintenant l’utilisation du plugin au sein du module app du projet Android. Rendez-vous donc dans le fichier "build.gradle.kts" qui se trouve, cette fois-ci, dans le dossier app de votre projet Android. Cherchez la sectionpluginset ajoutez-y une référence au plugin Hilt.

plugins {
  //…
  alias(libs.plugins.hilt)
}

5. Toujours au sein du fichier "build.gradle.kts" du module app, rendez-vous dans la sectiondependenciespour y ajouter les dépendances aux deux bibliothèques Hilt.

Si vous utilisez Java : 

Si vous utilisez Kotlin : 

dependencies {
  //…
  implementation(libs.hilt)
  annotationProcessor(libs.hilt.compiler)
}

 

dependencies {
  //…
  implementation(libs.hilt)
  ksp(libs.hilt.compiler)
}

Créez la classe d’application Hilt

Maintenant que Hilt est intégré dans le projet PETiSoin, il est temps d’implémenter les premiers éléments. Toutes les applications qui utilisent Hilt doivent contenir une classe Applicationannotée avec@HiltAndroidApp. Sinon, c’est le crash assuré ! 

Cette annotation déclenche la génération du code de Hilt, y compris une classe de base de votre application qui sert de conteneur de dépendances au niveau de l'application.

1. Vous devez donc créer une classe PETiSoinApplicationqui hérite de la classe Application.

Si vous utilisez Java : 

Si vous utilisez Kotlin :

@HiltAndroidApp
public final class PETiSoinApplication
    extends Application
{
}
@HiltAndroidApp
class PETiSoinApplication
    : Application() {
}

2. N’oubliez pas de mettre à jour le fichier "AndroidManifest.xml" du projet pour référencer cette nouvelle classe au sein de la balise<application>:

<?xml version="1.0" encoding="utf-8"?>
<manifest
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  >
  <application
    android:name=".PETiSoinApplication"
    android:allowBackup="true"
    android:dataExtractionRules="@xml/data_extraction_rules"
    android:fullBackupContent="@xml/backup_rules"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/Theme.HexagonalGames"
    tools:targetApi="31"
    >
  </application>
</manifest>

Créez le module Hilt

Si on anticipe l’architecture cible de l’application PETiSoin, nous allons implémenter le patron de conception appelé “Repository” pour exposer les données depuis la couche de données de notre architecture MVVM. Ce patron permet de gérer les sources de données de manière organisée.  Et pour interagir avec la base de données, le repository a besoin d’un DAO qu’il convient d’injecter dans son constructeur. Le problème est qu’un DAO est une interface. Il n’est donc pas possible d’injecter un constructeur. C’est pour répondre à cette problématique que nous devons créer un module Hilt pour fournir les informations de liaison et indiquer à Hilt comment fournir des instances de certains types.

Un module Hilt est une classe annotée avec@Module. Cette classe doit également porter une seconde annotation@InstallIn afin d’indiquer à Hilt la classe Android dans laquelle chaque module sera utilisé ou installé. Dans le cas de l’application PETiSoin, nous allons installer le module dans la classeApplicationen utilisant le composant  SingletonComponentde Hilt.

Vous pouvez donc créer une classeAppModuledans le package di du projet PETiSoin et lui faire porter les différentes annotations. Vous aurez l’occasion de la compléter dans la suite de ce chapitre.

Si vous utilisez Java : 

Si vous utilisez Kotlin : 

@Module
@InstallIn(SingletonComponent.class)
public final class AppModule
{
}
@Module
@InstallIn(SingletonComponent::class)
class AppModule {
}

Voici une vidéo qui récapitule les principales étapes pour mettre en place Hilt dans votre projet.

Appréhendez le patron de conception MVVM

Si l’on respecte un autre principe architectural poussé actuellement par Google pour le développement d’une application Android moderne, chaque application doit comporter au moins deux couches :

  • la couche de données (Model) qui contient la logique métier de l’application et expose les données de l'application (repositories et Room);

  • la couche d'interface utilisateur qui affiche les données de l'application à l'écran (View et Viewmodel).

la couche d'interface utilisateur (View et Viewmodel) communique avec la couche de données (Model)
Le patron de conception MVVM

Implémentez le patron de conception Repository

Maintenant que les bases de l’injection de dépendance sont en place, il est temps de s’attaquer à la couche de données de l’application PETiSoin. 

La couche de données est constituée de dépôts qui contiennent une ou plusieurs sources de données. Idéalement, et pour respecter le principe de source unique de vérité ou Single Source of Truth en anglais, il convient de créer une classe de dépôt pour chaque type de données géré dans votre application. 

La couche de données (Model) est composée de dépôts ou repositories et de source de données.
Architecture de la couche de données

Créez les dépôts AnimalsRepositoryet  NotesRepository

Dans le cadre de l’application PETiSoin, deux types de données sont gérés dans l’application : les animaux et les notes.

Pour respecter le principe de Single Source of Truth, il est donc nécessaire de créer deux classes de dépôts :AnimalsRepositoryetNotesRepository . Chacun de ces dépôts utilise un DAO comme source de données pour effectuer les opérations dans la base de données Room :AnimalDaopour la classe de dépôtAnimalsRepository  etNoteDaopour la classe de dépôtNotesRepository.

Vous pouvez donc créer une classeAnimalsRepositorydans un package  data.repositoryet lui passer, au sein de son constructeur le DAOAnimalDao.

Si vous utilisez Java : 

Si vous utilisez Kotlin : 

public final class AnimalsRepository
{
  private final AnimalDao animalDao;
  public AnimalsRepository(AnimalDao animalDao)
  {
    this.animalDao = animalDao;
  }
}
class AnimalsRepository(private val animalDao: AnimalDao) {
}

À ce stade, même si la structure est en place, ce n’est malheureusement pas suffisant pour indiquer à Hilt d’injecter le DAO dans la classe de dépôt. Pour le lui faire comprendre, il est nécessaire de compléter le code ci-dessus avec l'annotation  @Inject, au niveau du constructeur.

Si vous utilisez Java :                                                                            

Si vous utilisez Kotlin, il convient d’utiliser explicitement le mot cléconstructor.

public final class AnimalsRepository
{
  private final AnimalDao animalDao;
  @Inject
  public AnimalsRepository(AnimalDao animalDao)
  {
    this.animalDao = animalDao;
  }
}
class AnimalsRepository @Inject constructor(private val animalDao: AnimalDao) {
}

Définissez l’injection de la base de données

Puisque le DAO AnimalDaoest une interface, il est nécessaire de compléter la classeAppModuleafin d’indiquer à Hilt comment fournir les instances de ce DAO.

Dans le cas de l’application PETiSoin, le DAO vient de la base de données  PETiSoinDatabase. Vous pourriez donc utiliser la Singleton créée dans la partie précédente du cours pour récupérer le DAO. Mais puisqu’on utilise Hilt ici, vous pouvez également lui demander de se charger de maintenir le singleton de la base de données.

Dans la classeAppModule, créez une méthode provideDatabasequi renverra l’instance de la base de données.

Si vous utilisez Java :

Si vous utilisez Kotlin :

@Module
@InstallIn(SingletonComponent.class)
public final class AppModule
{
  public PETiSoinDatabase provideDatabase()
  {
  }
}
@Module
@InstallIn(SingletonComponent::class)
class AppModule {
  fun provideDatabase(): PETiSoinDatabase {
  }
}

Pour fournir la base de données, nous n’allons pas réutiliser l’implémentation du patron de conception Singleton mise en place précédemment. Nous allons repartir d’une feuille blanche et laisser Hilt gérer lui-même le Singleton. Vous allez donc créer vous-même l’instance de la base de données à l’aide des méthodes de Room et notamment la méthodedatabaseBuilder

  1. Puisque vous fournissez vous-même l’instance, vous devez ajouter l’annotation  @Providessur la méthode. 

  2. Et comme vous souhaitez appliquer le patron de conception Singleton sur la base de données pour limiter le nombre de connexion, l’annotation @Singletondoit également figurer.

Si vous utilisez Java :

Si vous utilisez Kotlin :

@Module
@InstallIn(SingletonComponent.class)
public final class AppModule
{
  @Provides
  @Singleton
  public PETiSoinDatabase provideDatabase()
  {
  }
}
@Module
@InstallIn(SingletonComponent::class)
class AppModule {
  @Provides
  @Singleton
  fun provideDatabase(): PETiSoinDatabase {
  }
}

La méthodedatabaseBuilder, qui permet de créer une instance de la base de données, nécessite unContexten tant que paramètre. Comment en obtenir un ?

Pas de panique ! Hilt a tout prévu. Il est possible d’injecter unContextdans la méthode en y ajoutant un attribut de typeContextsur lequel l’annotation @ApplicationContextest appliquée.

Si vous utilisez Java :

Si vous utilisez Kotlin :

@Module
@InstallIn(SingletonComponent.class)
public final class AppModule
{
  @Provides
  @Singleton
  public PETiSoinDatabase provideDatabase(@ApplicationContext Context context)
  {
  }
}
@Module
@InstallIn(SingletonComponent::class)
class AppModule {
  @Provides
  @Singleton
  fun provideDatabase(@ApplicationContext context: Context): PETiSoinDatabase {
  }
}

Il ne reste plus qu’à compléter le corps de la méthode avec la création de l’instance de la base de données.

Si vous utilisez Java :

Si vous utilisez Kotlin :

@Module
@InstallIn(SingletonComponent.class)
public final class AppModule
{

  @Provides
  @Singleton
  public PETiSoinDatabase provideDatabase(@ApplicationContext Context context)
  {
    return Room.databaseBuilder(context.getApplicationContext(), PETiSoinDatabase.class, "PETiSoinDatabase").build();
  }

}
@Module
@InstallIn(SingletonComponent::class)
class AppModule {

  @Provides
  @Singleton
  fun provideDatabase(@ApplicationContext context: Context): PETiSoinDatabase {
    return Room.databaseBuilder(context, PETiSoinDatabase::class.java, "PETiSoinDatabase").build()
  }

}

Définissez l’injection du DAO

Maintenant qu'Hilt sait comment gérer l’instance de la base de données, il est possible de compléter la classeAppModulepour lui indiquer comment injecter l’interface  AnimalDaodans la classeAnimalsRepository.

1. Dans la classeAppModule, créez une méthodeprovideAnimalDaoqui a pour paramètre une instance de la classePETiSoinDatabase et qui renvoie, depuis cette instance, le DAO. 

2. N’oubliez pas de faire porter l’annotation @Providesà la méthode pour indiquer à Hilt que c’est bien une instance que vous fournissez.

Si vous utilisez Java :

Si vous utilisez Kotlin :

@Module
@InstallIn(SingletonComponent.class)
public final class AppModule
{
  //...
  @Provides
  public AnimalDao provideAnimalDao(PETiSoinDatabase database) {
    return database.animalDao();
  }
}
@Module
@InstallIn(SingletonComponent::class)
class AppModule {
  //…
  @Provides
  fun provideAnimalDao(database: PETiSoinDatabase): AnimalDao =
    database.animalDao()
}

3. Finalement, il est possible de compléter la classeAnimalsRepositoryavec les différentes opérations CRUD.

Si vous utilisez Java :                                                                                                      

Si vous utilisez Kotlin : 

public final class AnimalsRepository
{
  private final AnimalDao animalDao;
  @Inject
  public AnimalsRepositoryJava(AnimalDao animalDao)
  {
    this.animalDao = animalDao;
  }
  public Animal getAnimalById(int id)
  {
    return animalDao.getAnimalById(id);
  }
  public void addAnimal(Animal animal)
  {
    animalDao.addAnimal(animal);
  }
  public void deleteAnimal(Animal animal)
  {
    animalDao.deleteAnimal(animal);
  }
  public LiveData<List<Animal>> getAllAnimals()
  {
    return animalDao.getAllAnimals();
  }
}
class AnimalsRepository @Inject constructor(private val animalDao: AnimalDao) {
  suspend fun getAnimalById(id: Long): Animal =
    animalDao.getAnimalById(id)
  fun getAllAnimals(): Flow<List<Animal>> =
    animalDao.getAllAnimals()
  suspend fun addAnimal(animal: Animal) {
    animalDao.addAnimal(animal)
  }
  suspend fun deleteAnimal(animal: Animal) {
    animalDao.deleteAnimal(animal)
  }
}

Voici une vidéo qui récapitule les principales étapes pour mettre en place la patron de conception Repository au niveau de la couche de données de l’architecture MVVM dans l’application PETiSoin.

À vous de jouer

Contexte

Il est temps de mettre en place les bonnes pratiques de développement de la section “santé” de l’application PETiSoin en débutant par la couche de données du patron de conception MVVM.

Consignes

Dans le projet, disponible sur GitHub (Java ou Kotlin), vous devez créer un dépôt VaccinesRepositoryqui contient une méthode qui renvoie l’ensemble des vaccins pour un animal. 

Vous n’allez pas mettre en place Hilt mais compléter l’implémentation qui est déjà en place.

Livrables

Vous pouvez dupliquer le projet GitHub pour modifier le code source du projet et fournir un projet dans l’ensemble des tests passe.

En résumé

  • L’injection de dépendances permet de rendre le code maintenable et évolutif.

  • Au lieu de créer les objets dont une classe a besoin à l'intérieur de celle-ci, on les instancie à l'extérieur et on les passe ensuite à la classe.

  • Le patron de conception Repository permet de respecter le principe de Single Source of Truth.

  • Le patron de conception MVVM est aujourd’hui l’architecture recommandée par Google dans le cadre du développement d’une application Android.

  • La couche de données du patron de conception MVVM inclut le patron de conception Repository et une source de données qui est la base de données Room.

Vous avez écrit la couche de données de l’application PETiSoin. Vous pouvez maintenant vous attaquer à la couche interface utilisateur pour restituer visuellement les données aux utilisateurs de l’application.

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