Utiliser Glide avec Kotlin, la meilleure librairie Android de chargement d’images !

Glide, c’est quoi ? Définition, implémentation et retour d’expériences sur la meilleure librairie Android de chargement d’images distantes ou locales !
Victor COLEAUMis à jour le 6 Août 2020
AXOPEN_Blog_Glide_Kotlin_Java.jpg

Glide, c’est quoi ?

Glide est une librairie Android Open Source de gestion du chargement et de l’affichage d’images pour vos applications mobiles.

Glide est simple à prendre en main, facile d’utilisation et propose de nombreuses fonctionnalités... Elle vous fera économiser du temps et de l’énergie dans tous vos développements !

Installation de Glide

La première chose à faire est d’intégrer Glide à votre projet. Pour cela rien de plus simple.

Si vous utilisez Maven :

<dependency>
    <groupId>com.github.bumptech.glide</groupId>
    <artifactId>glide</artifactId>
    <version>4.11.0</version>
</dependency>
<dependency>
    <groupId>com.github.bumptech.glide</groupId>
    <artifactId>compiler</artifactId>
    <version>4.11.0</version>
    <optional>true</optional>
</dependency>

Si vous utilisez Gradle :

repositories {
    google()
    jcenter()
}

dependencies {
    implementation ’com.github.bumptech.glide:glide:4.11.0’
    annotationProcessor ’com.github.bumptech.glide:compiler:4.11.0’
}

Glide a ensuite besoin d’une ou deux permissions pour fonctionner correctement. Rajoutez donc les lignes suivantes dans votre Manifest.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="your.package.name"

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

    <application>
      ...
    </application>
</manifest>
  • INTERNET : cette permission est nécessaire uniquement si vous voulez afficher des ressources Web.
  • ACCESS_NETWORK_STATE : cette permission n’est pas obligatoire mais elle apporte un confort d’utilisation. En effet, si l’utilisateur charge une activité sans connexion Internet, les images Web ne s’afficheront pas. Sans cette permission, il devra recharger l’activité pour charger les images. Avec, Glide détectera de lui-même l’accès réseau et rechargera les images sans avoir à relancer l’activité.

Débuter sur Glide

Maintenant que vous savez à quoi sert Glide et comment l’intégrer dans votre projet, il est temps de rentrer dans le vif du sujet et de commencer à coder !

Glide.with(context)
    .load(image)
    .into(view)

Voilà c’est fini !

Bon ok je vous explique.

On commence avec Glide.with(context).

C’est le point de départ de toute utilisation de Glide. La méthode static .with() crée un objet RequestManager lié au cycle de vie du paramètre passé. Il existe plusieurs versions de cette méthode .with(context), .with(activity), .with(fragment) et .with(view), utilisez celle dont le cycle de vie vous convient le mieux.

On arrive ensuite à la méthode .load(). C’est elle qui s’occupe d’aller chercher l’image à afficher. Plusieurs types de paramètres peuvent lui être passés, les plus courants sont :

  • un id de ressource Android (R.drawable.src)
  • un fichier local (File(app/src/main/res/etc))
  • une URL (https://homepages.cae.wisc.edu/~ece533/images/baboon.png)

Enfin .into() indique à Glide où il doit afficher l’image qu’il vient de charger. Il s’agira le plus souvent d’une ImageView AndroidAndroid est un système d'exploitation mobile basé sur Linux. classique, mais nous verrons plus tard des cas plus spécifiques.

A noter que le chargement factuel de l’image ne se fait pas à l’appel de .load() mais à l’appel de .into().

D’ailleurs, pas la peine de vous casser la tête avec les Threads ! Glide gère lui-même. Il effectue tous ses calculs sur un Thread secondaire pour ne revenir sur le principal que pour l’affichage.

Options de Glide

A partir d’ici, je dois vous parler de l’objet RequestOptions. Basiquement, toutes les options que nous allons voir par la suite peuvent être directement appliquées au RequestBuilder retourné par la méthode .load().

Glide.with(context)
    .load(image)
    .option1()
    .option2()
    .into(view)

Les options se situent bien entre .load() et .into().

Mais, si vous comptiez appliquer les mêmes options à plusieurs images, il peut être un peu redondant de toutes les appliquer à chaque fois.

C’est pour cela qu’existe le RequestOptions.

val options = RequestOptions()
                .option1()
                .option2()

...

Glide.with(context)
    .load(image1)
    .apply(options)
    .into(view1)

...

Glide.with(context)
    .load(image2)
    .apply(options)
    .into(view2)

Grâce à la méthode .apply() , on applique d’un coup toutes les options du RequestOptions et celui-ci pourra être utilisé à plusieurs reprises.

Glide : Les transformations

Le premier type d’option proposé par Glide sont les Transformations. Ce sont différentes modifications graphiques appliquées à l’image avant qu’elle ne s’affiche.

Pour cela on utilise la méthode .transform().

Glide.with(context)
    .load(image)
    .transform(transformation)
    .into(view)

Default transformations

Par défaut, Glide vous propose trois transformations pré-créées : CenterCrop, FitCenter et CircleCrop.

A noter que ces trois transformations ont chacune leur propre méthode : .centerCrop(), .fitCenter() et .circleCrop(), pas besoin donc de passer par .transform().

CenterCrop

Cette première transformation agrandit l’image jusqu’à ce que celle-ci occupe tout son contenant de manière centrée. Puis, les bouts qui dépassent sont découpés. Cette transformation est donc à préconiser dans le cas de grandes images ne contenant pas d’informations essentielles et pouvant être rognées si besoin (ex: Background).

Glide.with(context)
    .load(url)
    .centerCrop()
    .into(view)

ou :

Glide.with(context)
    .load(url)
    .transform(CenterCrop())
    .into(view)
``` ![centerCrop-min.png](https://assets.axopen.com/assets/uploads/center_Crop_min_0e860ecabb.png)

#### FitCenter
La transformation `FitCenter` est l’exacte inverse de la première. Plutôt que de rogner les bouts d’une image, elle va au contraire ajouter des bandes blanches (ou noires) au besoin afin de remplir le conteneur.
Cette transformation est donc à préconiser dans le cas d’images importantes ne pouvant être rognées.

```kotlin
Glide.with(context)
    .load(url)
    .fitCenter()
    .into(view)

ou :

Glide.with(context)
    .load(url)
    .transform(FitCenter())
    .into(view)
``` ![fitCenter.png](https://assets.axopen.com/assets/uploads/fit_Center_a02bdaf656.png)

#### CircleCrop
Cette dernière transformation a en fait deux effets. Elle commence par effectuer un `CenterCrop` à notre image, puis applique un masque rond au résultat.

```kotlin
Glide.with(context)
    .load(url)
    .circleCrop()
    .into(view)

ou :

Glide.with(context)
    .load(url)
    .transform(CircleCrop())
    .into(view)

Custom transformations

Si les quelques transformations vues ci-dessus ne vous suffisent pas, il est tout à fait possible d’en créer par vous-mêmes.

Pour cela, il vous faudra implémenter l’interface Transformation dans une nouvelle classe. En réalité, pour une image simple, étendre la classe abstraite BitmapTransformation suffira largement.

class MyCustomTransformation : BitmapTransformation() {
    companion object {
        private val ID: String = "com.axopen.glide.mycustomtransformation"
        private val ID_BYTES = ID.toByteArray(Charset.forName("UTF-8"))
    }
    
    override fun transform(pool: BitmapPool, 
                            toTransform: Bitmap, 
                            outWidth: Int, 
                            outHeight: Int): Bitmap {
        return toTransform
    }
    
    override fun equals(o: Object) {
        return o is MyCustomTransformation
    }
    
    override fun hashCode() {
        return ID.hashCode()
    }
    
    override fun updateDiskCacheKey(messageDigest: MessageDigest) {
        messageDigest.update(ID_BYTES)
    }
    
}

Notez bien que toute transformation custom doit implémenter les méthodes .equals(), .hashCode() et .updateDiskCacheKey(). De plus, celles-ci doivent intégrer dans leur logique les potentiels arguments passés en constructeur de votre classe.

Concernant la méthode .transform(), c’est elle qui doit retourner la nouvelle Bitmap, après modification. L’argument toTransform étant bien entendu l’image de base.

Une fois votre transformation custom créée, vous pouvez l’utiliser comme n’importe quelle autre.

Glide.with(context)
    .load(url)
    .transform(MyCustomTransformation())
    .into(view)

Multiple transformations

Par défaut, l’ajout successif de transformations ne prend en compte que la dernière.

Glide.with(context)
    .load(url)
    .fitCenter()
    .transform(MyCustomTransformation())
    .circleCrop()
    .into(view)

Ce code ignore les premiers fitCenter et MyCustomTransformation pour n’appliquer que le dernier circleCrop.

Pour remédier à cela il existe une solution toute simple : utiliser la méthode .transforms() (notez le ’s’ à la fin). Celle-ci prend en paramètres plusieurs transformations et les applique successivement (dans l’ordre dans lequel on lui a présentées).

Glide.with(context)
    .load(url)
    .transfoms(FitCenter(), MyCustomTransformation())
    .into(view)

Une autre solution consiste à créer un objet MultiTransformation et à simplement le donner à notre méthode .transform() en tant que transformation composite.

Glide.with(context)
    .load(url)
    .transfom(MultiTransformation(FitCenter(), MyCustomTransformation()))
    .into(view)

Gestion des erreurs sur Glide

Glide propose trois options afin de gérer au mieux les différents cas d’erreurs :

  • Placeholder
  • Error
  • Fallback

Placeholder

Un placeholder est une image temporaire, en attendant que la vraie image soit prête à être affichée. On utilise pour ça la méthode .placeholder().

Glide.with(context)
    .load(url)
    .placeholder(R.drawable.placeholder)
    .into(view)

Un placeholder ne peut être qu’une ressource locale.

Attention, les placeholder sont chargés sur le Main Thread. Évitez donc les images trop lourdes.

Error

Comme son nom l’indique, la méthode .error() définit quelle image sera affichée en cas d’erreur lors du chargement de celle attendue.

Glide.with(context)
    .load(null)
    .error(R.drawable.error)
    .into(view)

Le cas d’erreur le plus courant est l’absence de connexion Internet lors d’un chargement par URLUniform Ressource Locator.

Fallback

Enfin, le fallback (grâce à la méthode .fallback()) ne couvre qu’un cas bien particulier : celui où null est passé à la méthode .load().

Glide.with(context)
    .load(url)
    .fallback(R.drawable.fallback)
    .into(view)

Diagramme de décisions

Petit diagramme pour expliciter l’ordre et les conditions d’affichage de chacun. 

Graph1.png

Réutilisation et annulation

Ce que je ne vous ai pas dit au début à propos de la méthode .into() c’est que, non seulement elle définit où l’image doit s’afficher, mais elle vous renvoie aussi un objet Target (c’est une interface en réalité).

Dans la grande majorité des cas, vous n’aurez pas besoin de récupérer cet objet.

Mais, un cas en particulier peut vous y contraindre : si vous souhaiter charger plusieurs images dans le même conteneur.

val target = Glide.with(context)
                .load(url)
                .into(view)
                
...

Glide.with(context)
    .load(secondUrl)
    .into(target)

Comme vous le voyez, .into() peut aussi prendre en argument une Target. Dans ce cas, Glide se charge lui-même d’annuler la première requête (si celle-ci n’était pas terminée) et de libérer les ressources mémoire.

La deuxième utilité de la Target est de pouvoir utiliser la méthode .clear().

Glide.with(context)
    .clear(target)

Cela va annuler toute requête sur cette Target et libérer les ressources mémoire.

Par défaut, Glide appelle cette méthode de lui-même à la fin du cycle de vie de l’élément passé à .with() (Activity, Fragment, etc.). Vous n’avez donc pas à libérer vous-même les ressources à chaque fois.

Gestion de la mémoire sur Glide

Si Glide est aussi performant, c’est aussi grâce à sa gestion de la mémoire et ses mises en cache.

Nativement, Glide utilisera sa propre stratégie pour estimer où, quand et comment mettre en cache les ressources utilisées, aussi bien locales que distantes. Cependant, il vous donne aussi la possibilité de changer de stratégie selon vos besoins grâce à la méthode .diskCacheStrategy(). Les valeurs possibles sont :

  • ALL : Met en cache toutes les données encodées et décodées.
  • AUTOMATIC : Stratégie par défaut de Glide.
  • DATA : Met en cache les données avant qu’elles ne soient décodées.
  • NONE : Ne met jamais rien en cache.
  • RESOURCE : Met en cache les données après qu’elles soient décodées.
Glide.with(context)
    .load(url)
    .diskCacheStrategy(DiskCacheStrategy.ALL)
    .into(view)

De plus, Glide vous permet d’affiner vos requêtes grâce aux options suivantes :

  • .onlyRetrieveFromCache() : N’affichera l’image que si celle-ci est déjà en cache.
Glide.with(context)
    .load(url)
    .onlyRetrieveFromCache(true)
    .into(view)
  • .skipMemoryCache() : Cette ressource ne sera pas sauvée en cache.
Glide.with(context)
    .load(url)
    .skipMemoryCache(true)
    .into(view)

Télécharger les images avec Glide

Une dernière fonctionnalité bien pratique de Glide est de pouvoir sauvegarder sur votre smartphone les images téléchargées. Vous n’êtes même pas obligé de les afficher, vous pouvez simplement utiliser Glide comme module de sauvegarde.

Pour cela, il vous faut d’abord rajouter la méthode .asBitmap() qui va dire à votre RequestManager d’interpréter votre ressource comme une Bitmap (défaut : Drawable).

Puis simplement passer à .into() une Target custom.

Glide.with(context)
    .asBitmap()
    .load(url)
    .into(new SimpleTarget<Bitmap>() {
        override fun onResourceReady(Bitmap resource,Transition<? super Bitmap> transition) {
            save(resource);
        }
    })

La méthode .save() étant votre logique de sauvegarde.

Conclusion

Glide est l’une des meilleures, si ce n’est LA meilleure, librairie Android de chargement d’images distantes ou locales. Sa versatilité et sa simplicité sont ses deux plus gros atouts, sans parler de ses performances.

Bref, je ne peux que vous la conseiller dès que vous souhaitez utiliser des ressources Web dans votre application.

Et pour aller encore plus loin, je vous conseille la doc officielle de Glide et bien sûr, sa Javadoc.