Text in social and messaging apps

Text is at the heart of social applications, where users share thoughts, express emotions, and tell stories. This document explores how to work with text effectively, focusing on emoji, styling, and rich content integration.

Working with emoji

Emoji have become an integral part of modern communication, particularly in social and messaging apps. They transcend language barriers, allowing users to express emotions and ideas quickly and effectively.

Emoji support in Compose

Jetpack Compose, Android's modern declarative UI toolkit, simplifies emoji handling:

  • Input: The TextField component natively supports emoji input.
  • Display: Compose's Text component renders emoji correctly, ensuring their consistent appearance across devices and platforms that offer an emoji2-compatible downloadable fonts provider, such as devices powered by Google Play services.

Display emoji covers displaying emoji in Jetpack Compose, including how to ensure that your app displays the latest emoji fonts, how to make sure emoji work correctly if your app uses both Views and Compose in the same Activity, and how to troubleshoot when emoji aren't showing up as expected.

Emoji support in Views

If you're using Android Views, the AppCompat library 1.4 or higher provides emoji support for platform subclasses of TextView:

If you are creating a custom view that is a direct or indirect subclass of TextView, extend the AppCompat implementation (rather than the platform implementation) to get built-in emoji support. Support modern emoji shows how to test and troubleshoot your emoji integration, support emoji without AppCompat, support emoji in custom views, support alternate fonts or font providers, and more.

Using the Emoji Picker

The Jetpack Emoji Picker is an Android View that provides a categorized list of emoji, sticky variants, and support for recently used emoji — compatible across multiple Android versions and devices. It's an easy way to uplevel your app's emoji integration.

Begin by importing the library in build.gradle.

dependencies {
   implementation("androidx.emoji2:emojipicker:$version")
}

Using the Emoji Picker with Compose

You construct the Emoji Picker in Compose using the AndroidView composable. This snippet includes a listener that lets you know when an emoji is selected:

AndroidView(
    modifier = Modifier.fillMaxSize(),
    factory = { context ->
        val emojiPickerView = EmojiPickerView(context)
        emojiPickerView.setOnEmojiPickedListener(this::updateTextField)
        emojiPickerView
    },
)

Compose 1.7 adds lots of new functionality to BasicTextField, including support for TextFieldState, which can sit in your ViewModel:

private val emojiTextFieldState = TextFieldState()

@Composable fun EmojiTextField() {
    BasicTextField(
        state = emojiTextFieldState,
    )
}

You can use TextFieldState to insert text at the cursor position or replace the selected text, as if you were typing in the IME:

private fun insertStringAsIfTyping(textFieldState: TextFieldState, string: String) {
    textFieldState.edit {
        replace(selection.start, selection.end, string)
        // clear selection on replace if necessary
        if (hasSelection) selection = TextRange(selection.end)
    }
}

The callback can update the BasicTextField using the helper function:

private fun updateTextField(emojiViewItem: EmojiViewItem) {
    insertStringAsIfTyping(emojiTextFieldState, emojiViewItem.emoji)
}

Using the Emoji Picker with Views

The Emoji Picker also works well with Views.

Inflate the EmojiPickerView. Optionally set emojiGridColumns and emojiGridRows based on the desired size of each emoji cell.

<androidx.emoji2.emojipicker.EmojiPickerView
    …
    app:emojiGridColumns="9" />

Insert a character at the cursor position or replace the selected text:

// Consider unregistering/reregistering anyTextWatcher that you might have as part of this
private fun insertStringAsIfTyping(editText: EditText, string: String) {
    val stringBuffer = StringBuffer(editText.text.toString())
    val index = editText.selectionStart
    if ( !editText.hasSelection() ) {
        stringBuffer.insert(index, string)
    } else {
        stringBuffer.replace(index, editText.selectionEnd, string)
    }
    editText.setText(stringBuffer.toString())
    editText.setSelection(index + string.length)
}

Provide a listener to the picked emoji, and use it to append characters to the EditText.

// a listener example
emojiPickerView.setOnEmojiPickedListener {
   val editText = findViewById<EditText>(R.id.edit_text)
   insertStringAsIfTyping(editText, it.emoji)
}

Style text

By applying visual distinctions to text, such as font styles, sizes, weights, and colors, you can enhance the readability, hierarchy, and overall aesthetic appeal of the user interface of your social or messaging app. Text styles help users quickly parse information, distinguish between different types of messages, and identify important elements.

Style text in Compose

The Text composable offers a wealth of styling options, including:

  • Text Color: Set Color to make text stand out.
  • Font Size: Control FontSize for appropriate scale.
  • Font Style: Use FontStyle to italicize text.
  • Font Weight: Adjust FontWeight for bold, light, etc. text variations.
  • Font Family: Use FontFamily to choose a suitable font.
Text(
    text = "Hello 👋",
    color = Color.Blue
    fontSize = 18.sp,
    fontWeight = FontWeight.Bold,
    fontFamily = FontFamily.SansSerif
)

You can set these styling options consistently across your application using themes.

MaterialTheme(
    // This theme would include Color.Blue (as appropriate for dark and light themes)
    colorScheme = colorScheme,
    content = content,
    typography = Typography(
        titleLarge = TextStyle(
            fontSize = 18.sp,
            fontFamily = titleFont,
            fontWeight = FontWeight.Bold
        ),
    ),
)

Add multiple styles in text

You can set different styles within the same Text composable using an AnnotatedString.

AnnotatedString has a type-safe builder, buildAnnotatedString, to make it easier to create.

@Composable
fun MultipleStylesInText() {
    Text(
        buildAnnotatedString {
            withStyle(style = SpanStyle(color = Color.Blue)) {
                append("M")
            }
            append("y ")

            withStyle(style = SpanStyle(fontWeight = FontWeight.Bold, color = Color.Red)) {
                append("S")
            }
            append("tyle")
        }
    )
}

See Style text for lots more information about text styling in Compose, including adding a shadow, advanced styling with a Brush, and opacity.

Style text in Views

With Views, rely on styles and themes for consistent typography. See Styles and themes for more information on how to apply custom theming for the views in your app. If you want to set different styles within a single text view, see Spans for more information on how to change text in a variety of ways, including adding color, making the text clickable, scaling the text size, and drawing text in a customized way.

Support image keyboards and other rich content

Users often want to communicate using stickers, animations, and other kinds of rich content. To make it simpler for apps to receive rich content, Android 12 (API level 31) introduced a unified API that lets your app accept content from any source: clipboard, keyboard, or dragging and dropping. For backward compatibility with previous Android versions (currently back to API level 14), we recommend you use the Android Jetpack (AndroidX) version of this API.

You attach an OnReceiveContentListener to UI components and get a callback when content is inserted through any mechanism. The callback becomes the single place for your code to handle receiving all content, from plain and styled text to markup, images, videos, audio files, and others.

See Receive rich content to learn more about how to implement support in your app. Jetpack Compose now has dragAndDropSource and dragAndDropTarget modifiers to simplify implementing drag-and-drop within your app and between other apps.