Future-Proof Emojis on Android

I have been working on a number of messaging apps the past few years. Apart from text and images, Emoji is a popular way for users to express themselves in these apps.

New Emojis are being created at an accelerating pace. New ones are introduced at least twice a year and there are now more than 2600 emojis.

Apps mostly rely on the OS to render these emojis properly. Support for the latest emojis for both Android and iOS are tied to OS updates.

For apps on iOS, this is good since most users upgrade to the latest version of iOS within months of official release.

Android, on the other hand, takes a lot longer for its users to upgrade to the latest OS version.

Android 7.1 (Nougat) has support for Emoji 4.0 which was approved late 2016. Unfortunately, 7.1 is only in 1.2% of devices (as of August 27, 2017).

Also, 25% of Android devices are still on 4.4 and below which only supports Emoji 1.0 which was released in 2015.

If somebody included a 🤰 emoji in a message, users in Android devices below 7.1 will see a (tofu) instead.

One way to solve this issue is to embed an Emoji font like EmojiOne (https://www.emojione.com/) in your app. This way you can guarantee that all the emojis included in the font will be rendered properly in any Android device.

While this is good, there are some downsides.

When new emojis are released, you need to release a version of your app that would include a newer version of your emoji font. You would have to hope that your users would update your app so that the newer emojis would render properly.

Apart from this, the font adds some weight to your APK. EmojiOne, for example, adds about 7MB. This may make users think twice before downloading your app.

Enter EmojiCompat and Downloadable Fonts.

At Google I/O, one of the announcements that didn’t get much attention is EmojiCompat. Given how much Emojis are used, this new Android feature should have been gotten more love.

EmojiCompat breaks Emoji support away from OS and app updates for devices with at least API 19 (Kitkat) and above.

Once you integrate EmojiCompat along with Downloadable Fonts, your users would not need to wait for their phone to get the latest Android OS or download the latest version of your app for the newer Emojis to display properly.

It’s not only limited to displaying newer emojis but creating content containing them as well. EmojiCompat provides integration for IMEs (keyboard apps) to support them.

To get started on integrating EmojiCompat and Downloadable Fonts, check out https://developer.android.com/topic/libraries/support-library/preview/emoji-compat.html and https://github.com/googlesamples/android-EmojiCompat.

Here are some things I’ve learned while integrating EmojiCompat and Downloadable Fonts:

1. Whenever possible, use the built-in EmojiCompat views: EmojiAppCompatTextView, EmojiAppCompatEditText and EmojiAppCompatButton. If the app is running on devices lower than API 19 (Kitkat), it will just behave like AppCompatTextView.

The following is a snippet of EmojiTextViewHelper which is used as filter for the built-in EmojiCompat views.

public EmojiTextViewHelper(@NonNull TextView textView) {
    Preconditions.checkNotNull(textView, "textView cannot be null");
    mHelper = Build.VERSION.SDK_INT >= 19 ? new HelperInternal19(textView)
    : new HelperInternal();
    }

2. In case you can't use the built-in views, always handle the scenario where the Downloadable Font would not initialize properly especially when using EmojiCompat.get().process. There are different reasons why initialization could fail. It could be that the Play Services version does not support it or the device has no storage left for the font file.

There are 2 ways to handle this.

Use the initialization callbacks.

EmojiCompat.get().registerInitCallback(object : EmojiCompat.InitCallback() {
  override fun onInitialized() {
    textView.text = EmojiCompat.get().process(body)
  }

  override fun onFailed(throwable: Throwable?) {
    textView.text = body
  }
})

This also applies in the event wherein the view is rendered but the initialization of EmojiCompat has yet to happen or complete. In case EmojiCompat has already been initialized before getting into the class, it would immediately call onInitialized or onFailed.

Check using LoadState.

if (EmojiCompat.get().loadState == EmojiCompat.LOAD_STATE_SUCCEEDED) {
  textView.text = EmojiCompat.get().process(body)
} else {
  textView.text = body
}

This applies if you do not want to wait for the initialization to complete. LoadState values are LOAD_STATE_SUCCEEDED, LOAD_STATE_FAILED and LOAD_STATE_LOADING.

If you are using the built-in views, you do not need to handle initialization error since it would just behave like a regular AppCompatTextView.

Looking at the EmojiInputFilter source code, it skips processing the text if the LOAD_STATE is LOAD_STATE_FAILED.

switch (EmojiCompat.get().getLoadState()){
    case EmojiCompat.LOAD_STATE_SUCCEEDED:
        ...
        if (process && source != null) {
            ...
            return EmojiCompat.get().process(text, 0, text.length());
        }
        return source;
    case EmojiCompat.LOAD_STATE_LOADING:
        EmojiCompat.get().registerInitCallback(getInitCallback());
        return source;
    case EmojiCompat.LOAD_STATE_FAILED:
    default:
        return source;

3. Certain versions of Play Services v11 cannot update its own font so characters from Emoji 5.0 would not render properly. Your phone probably needs to be on v11.5 at least for them to work. Play Services are pushed out every 3 weeks though, so this would be a non-issue fairly soon.

4. Currently, you can only download font through Play Services. It basically includes all fonts on Google Fonts (https://fonts.google.com/) and others (e.g. Noto Color Emoji Compat).

There is no public doc yet on how to be your own Font Provider.

It’s also unclear if online font services would eventually be font providers. I’ve be wary though on relying on other sites (with the exception of Google Fonts) to host the fonts you need for your app. You don't know how long these services would exist.

5. Periodically, a font may have an update from the Font Provider (e.g. the Emoji font is supporting newer emojis). Checking for and updating to the latest version of the font is the responsibility of Play Services.

Whenever your app creates a FontRequest, it will just use the font right away if it has been already downloaded. It doesn’t check the provider if it’s the latest version or not.

6. When a font is downloaded, how it is stored depends on the OS version. On devices with Android API 21 (Lollipop) and above, font file is read from where it was downloaded. For the rest, the font file is copied to the app’s storage.

So much has been written about the sad state of Emojis on Android (e.g. https://blog.emojipedia.org/androids-emoji-problem/ and http://mashable.com/2017/01/12/google-android-emoji-problem/#ddY.xAWynqqw).

With EmojiCompat and Downloadable Fonts, Google has finally given developers a solution to this problem. This actually exceeds the way iOS handle newer Emoji since it’s not tied to OS updates at all. To add to this, Android beat iOS in supporting Emoji 5.0 (Unicode 10) by adding them on O Preview.

Another great benefit of this is having a consistent look of the emojis on every Android device since it overrides the embedded emojis by some Android device manufacturers (like Samsung!)




Thanks to Siyamed Sinir, , Clara Bayarri, Andrew Fonts and Seigo Nonaka from the Google Android team for my being my resource for this blog post.