Passion Android : à la découverte de Jetpack Emoji

Dans cette série d’articles, nos experts partent à la découverte des librairies que composent Jetpack et vous font part de leur avis !

Jetpack Quésaco ?

Jetpack est une suite de librairies développées par les équipes de Google sur la partie Android.

L’objectif de Jetpack est de fournir une suite d’outils permettant de développer une application Android native plus rapidement, plus simplement et plus qualitativement.

Jetpack n’impose rien, cependant une bonne partie des librairies proposées sont aujourd’hui devenues des références dans l’écosystème Android.

De plus, les librairies proposées par Jetpack font désormais partie intégrante de n’importe quelle application récente, tant par la qualité que le suivi de ces outils.

Si on veut résumer, Jetpack est devenu la référence pour développer une application Android native de façon propre et recommandée. Plus qu’une suite de librairies, c’est presque un écosystème de composants à part entière !

La liste des librairies fournies par Jetpack est longue, et disponible ici https://developer.android.com/jetpack/androidx/explorer?case=all les plus populaires restant appcompat, fragment, Material Design Components et room, depuis quelques temps on voit une recrudescence d’usage de hilt, navigation ou encore databinding !

La genèse avec AppCompat

Comme n’importe quel écosystème, Android évolue avec le temps, propose de nouvelles fonctionnalités et s’améliore en sécurité.

Du côté des développeurs, il est donc important de suivre les évolutions de la plateforme et de maintenir les dépendances à jour. 

Si un développeur prend le risque de ne pas mettre à jour son application, alors il est possible que son application ne fonctionne plus sur les appareils récents ou mis à jour.

Cependant, la contrepartie de cela, et cela semble logique, est que les anciens appareils, non maintenus, finissent par ne plus être supportés.

Si on remonte le fil du temps, il y a quelques années, Android proposait une série de librairies permettant de rendre son application compatible avec ces anciens appareils. Cette suite s’appelait appcompat.

Avec le temps, de plus en plus de librairies de compatibilité sont apparues. Google a aussi sorti son nouveau système de design avec Android Lollipop, le Material Design, et les librairies de composants qui vont avec.
Pour les développeurs, il devenait compliqué de s’y retrouver. Dans un effort de simplification, Google a tout réuni au sein d’une nouvelle suite d’outil : Jetpack (et en a profité pour changer son préfixe pour devenir AndroidX)

Les Emoji

Dans l’article d’aujourd’hui, nous allons nous attarder sur une librairie en particulier : emoji.

Les emoji sont devenues des compagnons ordinaires de notre vie de tous les jours.

Que cela soit via des messages, sur les réseaux sociaux ou même sur internet plus globalement.

Les emoji sont basées sur unicode, un standard informatique permettant d’échanger du texte (finalement une façon “d’encoder” un texte et de le “décoder” pour afficher la même chose entre l’envoyeur et le receveur).

Comme tout standard, unicode évolue avec le temps, il s’améliore, des ajouts sont faits.
Depuis la première version d’unicode en 1991, 23 versions ont vu le jour ! La dernière version en date, la 14 est apparue en 2021 ! 

Les emoji suivent unicode, selon emojipedia, 11 versions différentes d’emoji sont en circulation. Chaque version apporte son lot de nouvelles emoji et les constructeurs se vantent régulièrement de l’ajout de nouvelles emoji dans leurs systèmes.

Pour faire le lien avec ce qu’on disait sur la compatibilité, une question apparaît naturellement : Que se passe-t-il sur les anciennes versions ? Les nouvelles emoji sont-elles magiquement incluses ? La réponse est simple : non.

Le tofu

Sur Android, pour palier au manque d’une emoji sur le système, le texte affiche un caractère spéciale qu’on appelle “tofu” 

Voilà ce qu’il se passe lorsqu’Android doit afficher un texte, qu’il vienne d’un message ou d’un site web.

  1. Le message est un texte encodé avec unicode
  2. Android lit ce message et repère une suite de caractères spéciaux, par exemple ️ \uFE0F
  3. Via son dictionnaire interne, il sait que ce code correspond à l’emoji coeur rouge et donc affiche ❤️

Le problème se pose lorsque le dictionnaire d’Android n’est plus à jour, il repère donc un caractère spécial de part son format \uXXXX mais ne sait pas le convertir, de ce fait, il affiche le fameux tofu, un rectangle doté d’une croix.

La fragmentation d’Android

Cela peut paraître anecdotique, mais c’est assez impactant dans la vie de tous les jours pour vos utilisateurs.

En effet, la fragmentation est un des points faibles d’Android.

Si on considère l’état actuel de la distribution du parc fournie par Android Studio, pour toucher 90% des utilisateurs mobiles, il faut descendre jusqu’à Android 5.1, l’API 22.

Cela veut dire que nous devons considérer que nos applications seront téléchargées par des téléphones ayant

  • Android 11
  • Android 10
  • Android 9.0 (Pie)
  • Android 8.1 (Oreo)
  • Android 8.0 (Oreo)
  • Android 7.1 (Nougat)
  • Android 7.0 (Nougat)
  • Android 6.0 (Marshmallow)
  • Android 5.1 (Lollipop)

Android 5.1 étant sorti en 2015, on peut considérer qu’il y a une grande partie des emoji actuelles non gérées sur cet appareil…

Le problème, et le risque, est que de nos jours, on communique beaucoup par emoji.
Autant avant cela servait surtout à faire passer une humeur, autant désormais on peut remplacer des mots entiers par des emoji.

Un cas concret, que se passe-t-il si j’envoie le message “J’🥰 Android” à mon collègue sur son vieux téléphone ?
🥰 fait partie du pack Unicode 11.0, ajouté donc en 2018.
Si on se base sur les dates de sorties approximatives des versions d’Android, un téléphone qui n’est pas sur une version minimale de Nougat (5.1) n’affichera rien ou le caractère tofu.

Maintenant un cas plus récent, on veut envoyer le message, “tu es un vrai 🥷” à son collègue.
🥷 fait partie d’Unicode 13.0, donc c’est Android 11 minimum !! moins de 10% du parc pourra afficher cette icône si rien n’est fait ! 

Alors comment faire en sorte que tous nos utilisateurs visualisent la même chose et comprennent bien les messages que l’on veut faire passer ? 

Jetpack Emoji à la rescousse 

Comme on s’en doute, Google a rapidement pris conscience du problème et a créé pour cela emoji.

Le principe ? Via une librairie, on va remplacer le dictionnaire qui fait les conversions des emoji pour notre application. De ce fait, quand Android doit afficher du texte, il disposera du dernier dictionnaire en date et affichera le bon emoji plutôt que le caractère tofu.

Pour implémenter cela, rien de plus simple,  deux options sont possibles, une simple, mais gourmande, et une plus complexe mais légère.

Pour commencer, 2 librairies sont à ajouter à votre projet, dans le build.gradle de votre module

implementation "androidx.emoji:emoji:1.1.0"
implementation "androidx.emoji:emoji-appcompat:1.1.0"

Ces deux librairies vont permettre d’ajouter plusieurs choses à notre application.

La première va permettre de charger le fameux dictionnaire d’emoji en plus et de remplacer celui actuellement utilisé par notre application.
La seconde va ajouter une suite de fonctions et de Widgets permettant d’afficher de manière simple les emoji.

Ce qu’il faut savoir, c’est que ces ajouts prennent du temps, et le temps sur Android c’est sacré, une milliseconde de trop à faire attendre l’utilisateur sans action et c’est le plantage ANR (application not responding).

A l’inverse, si vous n’attendez pas, alors la librairie ne sera pas chargée avant l’affichage de vos textes et tout l’intérêt est perdu.

Pour cela, Android recommande de faire cette initialisation au démarrage de l’application (dans un splashscreen par exemple).

La solution FontRequest

Pour charger notre font, nous allons passer par 3 étapes

  • Initialiser notre librairie
  • Spécifier à Google le téléchargement de notre dictionnaire
  • Convertir les textes dans nos Widgets

L’avantage d’utiliser une FontRequest et que si une app le fait, les autres peuvent en profiter ! C’est le principe des Downloadable Fonts qui est utilisé sous le capot.

Initialisation de la librairie

  1. Un objet FontRequest qui va indiquer à Android d’aller charger une police spéciale (notre fameux dictionnaire).
  2. Une configuration du type FontRequestEmojiCompatConfig qui nous servira à initialiser notre convertisseur d’unicode vers emoji.

Une fois cela fait, il ne nous reste qu’à initialiser le convertisseur et démarrer notre application normalement.
Pour cela, on va le faire dans une Activity dédiée qui sert de “splashscreen”.

class SpashActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val fontRequest = FontRequest(
            "com.google.android.gms.fonts",
            "com.google.android.gms",
            "Noto Color Emoji Compat",
            R.array.com_google_android_gms_fonts_certs
        )

        val fontRequestConfig =
            FontRequestEmojiCompatConfig(applicationContext, fontRequest)
                .registerInitCallback(object : EmojiCompat.InitCallback() {
                    override fun onInitialized() {
                        super.onInitialized()
                        // Lets GO !!
                        finish()
                    }

                    override fun onFailed(throwable: Throwable?) {
                        super.onFailed(throwable)
                        // Lets GO !! But without emoji 😢
                        finish()
                    }
                })

        EmojiCompat.init(
            fontRequestConfig
                // Replace all emojis with the new font, even if Android knows them
                .setReplaceAll(true)
                .setUseEmojiAsDefaultStyle(true)
        )
    }
}

Plusieurs remarques:

  • On utilise Noto Color Emoji Compat, dont vous pouvez trouver les informations ici https://www.google.com/get/noto/help/emoji/, c’est cela qui va nous servir de dictionnaire, il est donc important que Google maintienne cette police à jour.
    (Le site, quant à lui, n’est pas à jour depuis 2017 https://www.google.com/get/noto/updates/ )
  • Un fichier de ressources, R.array.com_google_android_gms_fonts_certs, est utilisé comme argument.
    Ce fichier est quasiment impossible à trouver sur internet.
    Cependant, Android Studio a pensé à tout, pour le générer, il vous faudra ruser.

Spécifier à Google le téléchargement de notre dictionnaire

Un petit détour sur votre AndroidManifest.xml pour ajouter une balise meta ! 

Cela va servir à FontRequest pour un usage particulier.
En effet, le fonctionnement de FontRequest est de télécharger le dictionnaire à l’installation de l’application par le Play Store en passant par les play services.

Pour faire cela, on va ajouter dans notre AndroidManifest la balise suivante

<application
    ....>
    <meta-data
        android:name="fontProviderRequests"
        android:value="Noto Color Emoji Compat" />
</application>

Encore une fois, je vous invite à aller voir comment fonctionnent les Downloadable Fonts.

Convertir les textes dans nos Widgets

Il ne nous reste qu’une chose à faire !

En effet, ce n’est pas aussi magique que ça, il faut quand même qu’on demande à Android de convertir nos textes avec son dictionnaire ! Pour cela 2 options existent.

  • Utiliser un TextView classique, ou un androidx.appcompat.widget.AppCompatTextView ou tout autre Widget qui hérite de cela
  • Utiliser un TextView spécial, fourni par la librairie, androidx.emoji.widget.EmojiAppCompatTextView

Si on part sur l’option TextView classique, alors il faut convertir nous-même le texte avant de l’afficher, pour cela, on va faire

notreTextWidget.text = EmojiCompat.get().process(text)

Et si l’on passe par EmojiAppCompatTextView, alors il faut simplement faire

notreTextWidget.text = text 

et la conversion se fait toute seule !

Magiquement, on peut désormais afficher nos emoji sur tous les appareils ! Peut-importe la version.

Note : au jour où j’écris ces lignes, certains problèmes peuvent apparaître. En effet, la libraire se basant sur les Play Services, il est possible que le rendu sur emulateur ne fonctionne pas avec cette méthode.

Un ticket est ouvert sur le github de la font ainsi que sur le google bug tracker !

Cela nous amène donc à une deuxième solution.

Le BundledEmojiCompatConfig

Si on n’a pas envie de passer par les Play Services (ou si par exemple notre application est distribuée sur plusieurs Stores), alors une autre solution existe.

Celle-ci permet d’embarquer directement le dictionnaire à la compilation.

L’avantage est que la font est directement disponible, cependant, deux inconvénients apparaissent.

  • Il faut mettre à jour son application dès qu’une nouvelle version de la librairie sort (finalement dès qu’une nouvelle version d’emoji est intégrée)
  • Le bundle/apk généré est lesté de quelques Mo (même quelques 10aines de Mo…)

Cependant, si cette solution vous intéresse, alors elle est très simple, 2 étapes:

  • Ajouter une librairie au projet (bah oui, celle qui va contenir notre dictionnaire)
  • Changer la configuration

Ajout au build.gradle

Notre build.gradle va donc se voir ajoutée d’une     

implementation "androidx.emoji:emoji-bundled:1.1.0"

Jusque là, facile.

Spécifier la configuration

Nous allons maintenant changer notre SpashActivity par 

class SpashActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    
        val fontRequestConfig =
            BundledEmojiCompatConfig(this)
                .registerInitCallback(object : EmojiCompat.InitCallback() {
                    override fun onInitialized() {
                        super.onInitialized()
                        // Lets GO !!
                        finish()
                    }

                    override fun onFailed(throwable: Throwable?) {
                        super.onFailed(throwable)
                        // Lets GO !! But without emoji 😢
                        finish()
                    }
                })

        EmojiCompat.init(
            fontRequestConfig
                .setReplaceAll(true)
                .setUseEmojiAsDefaultStyle(true)
        )
    }
}

De là votre application est lancable directement et compatible avec les emoji ! Pas besoin de re-télécharger une font 🎉.

En conclusion

Finalement, qu’est-ce qu’on peut retenir de tout cela:

  • Les emoji font partie intégrante de nos vies, le nier c’est faire une politique de l’autruche
  • Vos utilisateurs Android n’ont pas tous la chance d’avoir la dernière version ou le dernier appareil ! Pensez à eux. 
  • Dans notre exemple on part sur la fragmentation classique d’Android, mais si votre application est déjà en prod alors vous avez probablement des analytics plus poussées
  • Si les conversations textuelles sont une part importante de votre application, cela est un vrai quick win. 
  • Jetpack nous donne encore un outil de plus pour rendre notre application plus compatible avec de nombreuses versions, de manière simple et efficace !

En aparté

Vous avez dû constater que certaines applications, comme WhatsApp, disposent de tous les emojis avec un format particulier. Cela est tout à fait possible avec le même système, WhatsApp embarque ses propres emojis et peut donc tout afficher ! 

C’est pour cela que vous ne verrez probablement jamais le tofu dans vos conversations WhatsApp.

Le projet qui contient la démo de ce que nous avons présenté aujourd’hui se trouve sur notre dépôt Github public : https://github.com/LaMobilery/android-jetpack


Note de la rédaction

Même si EmojiCompat est top, cela reste fait par des Hommes et donc faillible ! Lors de nos tests, nous avons remarqué que certaines emoji ne chargaient pas correctement.
De plus sur certains téléphones (OS) / emulateurs, si les play services ne sont pas à jour ou pas dispos, la FontRequest échoue. De l’importance de la Bundled version.

Fort heureusement, des moyens de remonter les problèmes existent, la police dispose de son dépôt github et l’issue tracker de Google a déjà quelqus remontées.