Skip to content

kosi-libs/Emoji.kt

Repository files navigation

Emoji.kt: Kotlin/Multiplatform Emoji

Emoji.kt Core provides Kotlin/Multiplatform support for:

  • Displaying emoji with system font.
    Such as: "Hello ${Emoji.Wink}, have a great day ${Emoji.WavingHand.mediumDark}, ${Emoji.RedHeart} you!".

  • Parsing a String to locate and extract its emojis.
    Such as: finder.findEmoji("Hello πŸ˜‰, have a great day πŸ‘‹πŸΎ, ❀️ you!").

  • Listing Emojis.
    With: Emoji.all().

  • Parsing a String with short-codes and emoticons.
    Such as: catalog.replace("Hello :wink:, have a great day :waving-hand~medium-dark:, <3 you!").

  • Getting emoji information.
    Such as: Emoji.Wink.details.description.

  • Exploring emoji by groups & subgroups.
    With: Emoji.allEmojiGroups() and Emoji.subgroupsOf(group).

Emoji.Compose provides Compose/Multiplatform support for:

  • Displaying Emoji with Noto vector images.
    With: TextWithNotoImageEmoji(text).

  • Displaying Emoji with Noto vector animations.
    With: TextWithNotoAnimatedEmoji(text).

  • Using system font if supported and reverting to Noto images if it is not (on Wasm).
    With: TextWithPlatformEmoji(text).

  • Handling how images & animations are downloaded.
    With: ProvideEmojiDownloader(myDownloadFunction) { content() }.

Demo

Emoji.kt Core

build.gradle.kts
implementation("org.kodein.emoji:emoji-kt:2.0.1")

Displaying Emoji in a Kotlin String

You can insert Emoji in a Kotlin String with the Emoji companion object extensions:

val str = "Hello ${Emoji.Wink}, have a great day ${Emoji.WavingHand}, ${Emoji.RedHeart} you!"

Some emoji can be specialized with skin tones:

val str = "Hello ${Emoji.Wink}, have a great day ${Emoji.WavingHand.mediumDark}, ${Emoji.RedHeart} you!"

Parsing a String to locate and extract its emojis

To parse a String with emojis, you first need to create an EmojiFinder.

Caution

Note that creating an EmojiFinder is expensive and can take a few milliseconds. It is therefore advised that you:

  • Create the EmojiFinder in background (for example in Dispatchers.Default if using coroutines).

  • Keep a global reference to use throughout your application and construct it only once.

Note that EmojiFinder is immutable and therefore thread-safe.

You can then use this EmojiFinder to extract emojis in your strings:

val emojiFinder = withContext(Dispatchers.Default) { EmojiFinder() }

val str = "Hello πŸ˜‰, have a great day πŸ‘‹πŸΎ, ❀️ you!"
emojiFinder.findEmoji(str).forEach { found ->
    println("Found \"${found.emoji.details.description}\" at ${found.start}.")
}

Listing Emojis.

You can access a list of all known emoji with Emoji.all().

Caution

Note that creating the list of all known emoji with Emoji.all is expensive and can take a few milliseconds (this is a list of 1898 Emoji objects). It is therefore advised that you:

  • Create this list in background (for example in Dispatchers.Default if using coroutines).

  • Keep a global reference to use throughout your application and construct it only once.

val allEmoji = withContext(Dispatchers.Default) { Emoji.all() }

Parsing a String with short-codes and emoticons.

To parse a String, with short-codes and emoticons you first need to create an EmojiTemplateCatalog.

Caution

Note that creating an EmojiTemplateCatalog is expensive and can take a few milliseconds. It is therefore advised that you:

  • Create the EmojiTemplateCatalog in background (for example in Dispatchers.Default if using coroutines).

  • Keep a global reference to use throughout your application and construct it only once.

Note that EmojiTemplateCatalog is immutable and therefore thread-safe.

To create the EmojiTemplateCatalog, you need to pass to its constructor the list obtained with Emoji.all.

val allEmoji = withContext(Dispatchers.Default) { Emoji.all() }
val emojiCatalog = withContext(Dispatchers.Default) { EmojiTemplateCatalog(allEmoji) }

val str = emojiCatalog.replace("Hello :wink:, have a great day :waving-hand~medium-dark:, <3 you!")

An emoji can be described with:

  • A simple short-code, such as :wink:

  • A short-code with one skin tone, such as :waving-hand~medium-dark:

  • A short-code with two skin tones, such as :people-holding-hands~medium-light,medium-dark:

You can add your own short-codes and emoticons when constructing the EmojiTemplateCatalog:

val emojiCatalog = withContext(Dispatchers.Default) {
    EmojiTemplateCatalog(allEmoji) {
        addAlias("hello", Emoji.WavingHand)
        addEmoticon("^^'", Emoji.GrinSweat)
    }
}

Getting emoji information

All emojis are described through their Emoji.Details data class.
You can access:

  • emoji.details.string: The UTF-16 String containing the emoji.

  • emoji.details.description: The description of this emoji as given by the Unicode standard.

  • emoji.details.unicodeVersion: The emoji unicode definition minimum version where this emoji appears.

  • emoji.details.aliases: The list of emoji aliases, as defined by the Unicode standard and the Noto font.

  • emoji.details.emoticons: The list of emoticons that links to that emoji (such as ;) or ^_^;.

  • emoji.details.notoAnimated: Whether this emoji is provided as an animation by the Noto font.

  • emoji.details.codePoints(): The list of Unicode code-points of this emoji.

Exploring emoji by groups subgroups

You can get:

  • All emoji groups: val groups: List<String> = Emoji.allGroups()

  • All emoji groups and subrougps: val groups: List<Pair<String, String>> = Emoji.allSubgroups()

  • All emoji subrougps of a group: val groups: List<String> = Emoji.subgroupsOf(group)

  • All emoji of a group: val groupEmoji: List<Emoji> = Emoji.allOf(group)

  • All emoji of a subgroup: val groupEmoji: List<Emoji> = Emoji.allOf(group, subgroup)

Emoji.Compose

build.gradle.kts
implementation("org.kodein.emoji:emoji-compose-m2:2.0.1") // With compose.material
// OR
implementation("org.kodein.emoji:emoji-compose-m3:2.0.1") // With compose.material3
Note

Emoji.Compose is not needed if you simply wish to display platform Emojis in a Compose application.
It is needed if any of the following is true:

  • You are targeting WASM, as WASM does not support displaying platform emoji.

  • You want to use Noto images or animations.

Initializing the Emoji Service

The first time you use emoji composable functions, the emoji service will need to initialise, which may take a few milliseconds. You can initialise the emoji service ahead of time so that even the first emoji composable function invocation will be instantaneous:

@Composable
fun App() {
    remember { EmojiService.initialize() } //(1)
    AppContent()
}
  1. Service initialization happens in the background and will not block the UI thread.

Displaying Emoji with Noto vector images

You can display an Emoji Image with NotoImageEmoji:

NotoImageEmoji(Emoji.Wink, Modifier.fillMaxSize())

You can display a String by replacing all of its emojis by images downloaded from the Noto font image library.

TextWithNotoImageEmoji(
    text = "Hello ${Emoji.Wink}, have a great day ${Emoji.WavingHand.mediumDark}, ${Emoji.RedHeart} you!"
)

Note that if you want to use short-codes and emoticons, you need to parse the string with String.withEmoji first:

TextWithNotoImageEmoji(
    text = "Hello :wink:, have a great day :waving-hand~medium-dark:, <3 you!".withEmoji()
)

Displaying Emoji with Noto vector animations

Instead of using Noto images, you can use animations, if the emoji supports it.

NotoAnimatedEmoji(Emoji.Wink, Modifier.fillMaxSize())
TextWithNotoAnimatedEmoji(
    "Hello ${Emoji.Wink}, have a great day ${Emoji.WavingHand.mediumDark}, ${Emoji.RedHeart} you!"
)
Note
If the emoji does not support animation, than it will be displayed as a still image.

Using system font if supported and reverting to Noto images if it is not (on Wasm).

At the moment, Compose Wasm does not support displaying system font emoticons. To circumvent that, WithPlatformEmoji changes the provided text only on Wasm to insert images instead of font emoticons. On all other platforms, however, the emoji will not be replaced.

WithPlatformEmoji(
    "Hello ${Emoji.Wink}, have a great day ${Emoji.WavingHand.mediumDark}, ${Emoji.RedHeart} you!"
) { text, inlineContent ->
    Text(text = text, inlineContent = inlineContent)
}

Handling downloads

Emoji.Compose does not depend on a particular HTTP library. It therefore offers the simplest of downloader: no retry support, no cache or offline support, etc.

If you are using Ktor, Coil, or any other multiplatform HTTP library, you can easily use it in Emoji.Compose:

ProvideEmojiDownloader(
    download = {
        val response = ktorClient.get(it.url)
        response.body<ByteArray>()
    }
) {
    App()
}

Creating AnnotatedString

If you want to manipulate the annotatedString produced by the TextWith*Emoji functions, you can instead use the With*Emoji functions:

WithNotoImageEmoji(
    "Hello ${Emoji.Wink}, have a great day ${Emoji.WavingHand.mediumDark}, ${Emoji.RedHeart} you!"
) { text, inlineContent -> //(1)(2)
    // ...
}
  1. text: AnnotatedString

  2. inlineContent: Map<String, InlineTextContent>

Customizing the Emoji Service

The EmojiService is the global reference to the EmojiFinder and EmojiTemplateCatalog used by this library.
You can access it with:

  • @Composable fun EmojiService.get(): EmojiService?

  • suspend fun EmojiService.await(): EmojiService.

Before accessing it, you can add your own aliases and emoticons to the catalog:

EmojiService.catalogBuilder = {
    addAlias("hello", Emoji.WavingHand)
    addEmoticon("^^'", Emoji.GrinSweat)
}