---
source_url: https://www.pubnub.com/docs/chat/community-supported/android/message-reactions
title: Message reactions (emojis) for PubNub Chat Components for Android
updated_at: 2026-05-25T11:25:10.745Z
---

> Documentation Index
> For a curated overview of PubNub documentation, see: https://www.pubnub.com/docs/llms.txt
> For the full list of all documentation pages, see: https://www.pubnub.com/docs/llms-full.txt


# Message reactions (emojis) for PubNub Chat Components for Android

One emoji is sometimes worth a thousand words. Adding emojis under messages in chat apps makes your conversations way more engaging and emotional. It improves the overall user experience by adding a visual touch to it. It's particularly useful in group and social chat use cases to boost community engagement.

PubNub Chat Components for Android support emojis in conversations through the so-called `message reactions`. This out-of-the-box feature is available for both 1:1 and group chats and comes with six default emojis. After [enabling message reactions](#enable-reactions) in your application, you can customize them to your liking.

![Message Reactions](https://www.pubnub.com/assets/images/android-message-reactions-dd6afe960926df55634b3725c14ed14b.png)

## Enable reactions

:::note Message Persistence
PubNub Chat Components for Android use PubNub's [Message Reactions API](https://www.pubnub.com/docs/sdks/kotlin/api-reference/message-actions) to store information on added and removed message reactions. To use it in your app, make sure you have Message Persistence enabled on your app's keyset in the [Admin Portal](https://admin.pubnub.com/).
:::

By default, message reactions are disabled. To enable them in your chat app, you have to add proper logic to your application code.

Check how to do that based on the [Getting Started with Message Reactions](https://github.com/pubnub/chat-components-android-examples/tree/master/getting-started-with-reactions) app. To enable reactions, you must add a new `Menu.kt` file that defines the [BottomMenu](https://material.io/components/sheets-bottom) behavior and modify the existing `Chat.kt` file to update your chat application code.

Follow the steps in each file.

### Menu.kt

Create a `Menu.kt` file under the `ui.view` package and define the `Menu` composable function.

```kotlin
@Composable
fun Menu(
    visible: Boolean,
    message: MessageUi.Data?,
    onAction: (MenuAction) -> Unit,
    onDismiss: () -> Unit,
    modifier: Modifier = Modifier,
) {
    BottomMenu(
        message = message,
        headerContent = {
            DefaultReactionsPickerRenderer.ReactionsPicker { reaction ->
                message?.let { onAction(React(reaction, message)) }
            }
        },
        onAction = { action ->
            onAction(action)
        },
        onDismiss = onDismiss,
        visible = visible && message != null,
        modifier = modifier,
    )
}
```

`BottomMenu` is the drawable box that pops up when you tap the message you want to react to. It is enabled by default in the PubNub Chat Components for Android implementation and you can call it from any place in the application layout. To enable message reactions on `BottomMenu`, you need to pass the `DefaultReactionsPickerRenderer` implementation to the `headerContent` parameter as in the provided example.

:::tip Menu.kt
For reference, check the final version of the `Menu.kt` file in the [Getting Started with Message Reactions](https://github.com/pubnub/chat-components-android-examples/tree/master/getting-started-with-reactions/src/main/java/com/pubnub/components/example/getting_started/ui/view/Menu.kt) app.
:::

### Chat.kt

Open the `Chat.kt` file and follow these steps:

1. Add the onReactionSelected parameter to the Content composable function. 1 internal fun Content(2 messages: Flow<PagingData<MessageUi>>,3 presence: Presence? = null,4 onMessageSelected: (MessageUi.Data) -> Unit,5 // Add the below line6 onReactionSelected: ((React) -> Unit)? = null,7 )
2. Pass this new parameter to the MessageList component. 1 MessageList(2 messages = messages,3 presence = presence,4 onMessageSelected = onMessageSelected,5 // Add the below line6 onReactionSelected = onReactionSelected,7 modifier = Modifier8 .fillMaxSize()9 .weight(1f, true),10 )
3. Initialize reactionViewModel in the View function. 1@Composable2fun View(3 channelId: ChannelId,4) {5 val messageViewModel: MessageViewModel = MessageViewModel.defaultWithMediator()6 val messages = remember(channelId) { messageViewModel.getAll(channelId) }7 8 // Add the below lines9 val reactionViewModel: ReactionViewModel = ReactionViewModel.default()10 DisposableEffect(channelId){11 reactionViewModel.bind(channelId)12 onDispose {13 reactionViewModel.unbind()14 }15 }16 ...17}
4. Add the menuVisible and selectedMessage states to the View function. These states are responsible for the BottomMenu visibility and saving information on the last selected message. 1@Composable2fun View(3 channelId: ChannelId,4) {5 val messageViewModel: MessageViewModel = MessageViewModel.defaultWithMediator(channelId)6 val messages = remember { messageViewModel.getAll() }7 8 val reactionViewModel: ReactionViewModel = ReactionViewModel.default()9 DisposableEffect(channelId){10 reactionViewModel.bind(channelId)11 onDispose {12 reactionViewModel.unbind()13 }14 }15 16 // Add the two below lines17 var menuVisible by remember { mutableStateOf(false) }18 var selectedMessage by remember { mutableStateOf<MessageUi.Data?>(null) }19 ...20}
5. Define the onDismiss method.

```kotlin
val onDismiss: () -> Unit = { menuVisible = false}
```

1. Add the whole Menu component under CompositionLocalProvider and define the actions for it. 1Menu(2 visible = menuVisible,3 message = selectedMessage,4 onDismiss = onDismiss,5 onAction = { action ->6 when (action) {7 is Copy -> {8 messageViewModel.copy(AnnotatedString(action.message.text))9 }10 is React -> reactionViewModel.reactionSelected(action)11 else -> {}12 }13 onDismiss()14 }15)
2. Add the additional onMessageSelected and onReactionSelected parameters to the Content invoke function under CompositionLocalProvider. 1Content(2 messages = messages,3 // Add the "onMessageSelected" parameter4 onMessageSelected = {5 selectedMessage = it6 menuVisible = true7 },8 // Add the "onReactionSelected" parameter9 onReactionSelected = reactionViewModel::reactionSelected,10)

:::tip Chat.kt
For reference, check the final version of the `Chat.kt` file in the [Getting Started with Message Reactions](https://github.com/pubnub/chat-components-android-examples/tree/master/getting-started-with-reactions/src/main/java/com/pubnub/components/example/getting_started/ui/view/Chat.kt) app.
:::

## Default reactions

We provide six default message reactions to select from:

![Bottom Menu](https://www.pubnub.com/assets/images/bottom-menu-android-c139b3ca75deb3253a458a8e0e7a39c4.png)

Message reactions are based on [Unicode characters](https://unicode.org/emoji/charts/full-emoji-list.html) as surrogate pairs. Their full default list is defined in `MenuDefaults.kt` that's referenced in `DefaultReactionsPickerRenderer.kt`.

```kotlin
object DefaultReactionsPickerRenderer : ReactionsRenderer {
    ...
    var emojis: List<Emoji> = MenuDefaults.reactions()
    ...
}
```

Check the default values in `MenuDefaults.kt`:

```kotlin
fun reactions() = listOf(
    UnicodeEmoji("\uD83D\uDC4D"),    // 👍 thumbs up
    UnicodeEmoji("\u2764"),          // ❤ red heart U+2764
    UnicodeEmoji("\uD83D\uDE02"),    // 😂 face with tears of joy U+1F602
    UnicodeEmoji("\uD83D\uDE32"),    // 😲 astonished face U+1F632
    UnicodeEmoji("\uD83D\uDE22"),    // 😢 crying face U+1F622
    UnicodeEmoji("\uD83D\uDD25"),    // 🔥 fire U+1F525
)
```

The `UnicodeEmoji` key enforces that a given emoji must be of the `"reaction"` type and the `String` value (for example, `"\uD83D\uDC4D"` for "thumbs up"), as defined in the `Reaction.kt` file.

```kotlin
data class UnicodeEmoji(override val type: String, override val value: String) : Emoji() {
    constructor(value: String) : this("reaction", value)
}
```

:::tip Message Reactions API
`"reaction"` is a message action type that's required by PubNub's [Message Reactions API](https://www.pubnub.com/docs/sdks/kotlin/api-reference/message-actions) that PubNub Chat Components for Android communicate with to store information on added and removed reactions.
:::

### Replace default reactions

You can override the default emojis provided by `MenuDefaults.kt` by defining a new set of emojis in your application code. You should run this code in your app during initialization, before calling `DefaultReactionsPickerRenderer`.

Check this example:

```kotlin
setContent {
    AppTheme(pubNub = pubnub) {
        DefaultReactionsPickerRenderer.emojis = listOf(
            UnicodeEmoji("\uD83D\uDE4A"),    // 🙊
            UnicodeEmoji("\uD83D\uDE49"),    // 🙉
            UnicodeEmoji("\uD83D\uDE48"),    // 🙈
            UnicodeEmoji("\uD83D\uDC12"),    // 🐒
            UnicodeEmoji("\uD83E\uDD8D"),    // 🦍
            UnicodeEmoji("\uD83E\uDD84"),    // 🦄
        )
    }
}
```

### Change the default reactions count

PubNub Chat Components for Android provide six default emojis that appear in the `BottomMenu` row.

```kotlin
object DefaultReactionsPickerRenderer : ReactionsRenderer {
    ...
    var visibleItemsCount: Int = 6
    ...
}
```

You can change this default number. To set it to `4`, override the `visibleItemsCount` parameter in your application code.

```kotlin
setContent {
    AppTheme(pubNub = pubnub) {
        DefaultReactionsPickerRenderer.visibleItemsCount = 4
    }
}
```

There is no limitation as to the total emojis count that can be shown in `BottomMenu`. If you provide a number larger than the default `6`, the rest of the icons will be rendered under the default `BottomMenu` row and you can access them upon scrolling.

:::warning Limitations for negative values
The **visibleItemsCount** parameter is not validated for negative values so don't set it to `<= 0`.
:::

## Add reactions

If message reactions are [enabled](#enable-reactions) in your app, you can add them to a message in one of these ways:

* Long press the chosen message and select one of the six predefined emojis from a drawer that pops up at the bottom of the screen (BottomMenu). This way you can also remove a previously added reaction.
* Tap a reaction added by another user and the total count of reactions will increment in the reaction box under the message. This way you can also remove a previously added reaction.

## Cumulative reactions

There's a counter next to every added message reaction that's incremented or decremented in real-time as users tap the emojis to either add or remove them. This list is sorted in the reaction box by the message reaction timestamp.

If the reaction counter is more than 99, it displays `99+`.

This configuration is defined in the `DefaultReactionsPickerRenderer.kt` file.

```kotlin
text = if (reaction.members.size < 100) "${reaction.members.size}" else "99+"
```

## Reaction theme

To distinguish your reactions from those added by others, their background is highlighted in red.

`DefaultReactionsPickerRenderer` defines this behavior.

```kotlin
val reactionTheme =
                    if (reaction.members.any { it.id == currentUserId }) theme.selectedReaction else theme.notSelectedReaction
```

The choice of the appropriate theme for your and other reactions is defined in `ReactionTheme`.

```kotlin
class ReactionTheme(
    ...
    selectedReaction: ButtonTheme,
    notSelectedReaction: ButtonTheme,
    ...
)
```

## Reaction size

The size of reactions in `BottomMenu` depends on the device width and the number of available reactions. `ReactionsPicker` calculates it by dividing the maximum width of the device by the total number of selected message reactions.

```kotlin
itemWidth = floor(this.maxWidth.value / visibleItemsCount).dp
```

`ReactionsPicker` (in `DefaultReactionsPickerRenderer.kt`) sets the 1:1 ratio for the message reactions, so if you set one item (icon) in a row, this item will be a square with a width equal to the device width.

## Communication with PubNub

Message reactions interact with the [MessageList](https://www.pubnub.com/docs/chat/community-supported/android/ui-components#messagelist) component. The logic that defines what is happening upon long-tapping a message is invoked in [MessageRenderer](https://www.pubnub.com/docs/chat/community-supported/android/ui-components#messagelist) through these four methods in the `Message` composable function.

```kotlin
interface MessageRenderer {
    @Composable
    fun Message(
        ...
        reactions: List<ReactionUi>,
        onMessageSelected: (() -> Unit)?,
        onReactionSelected: ((Reaction) -> Unit)?,
        reactionsPickerRenderer: ReactionsRenderer,
    )
    ...
}
```

* **reactions** stands for the list of reactions to a specific message.
* **onMessageSelected** opens up `BottomMenu` with available reactions to pick when you long-tap a message.
* **onReactionSelected** stands for the action that's to be called when you choose a reaction.
* **reactionsPickerRenderer** is the default renderer that defines how the selected message reaction is displayed.

Long-tapping a message and choosing a reaction triggers [reactionViewModel](https://www.pubnub.com/docs/chat/community-supported/android/ui-components#view-model-3) which acts as an intermediary between the UI layer, services, and repositories. `reactionViewModel` uses [messageActionRepository](https://www.pubnub.com/docs/chat/community-supported/android/data-components#default-message-reaction-service) to retrieve information on the selected reaction from the local Room database to verify who, when, and where (on which channel) selected a given reaction.

```kotlin
fun reactionSelected(reaction: PickedReaction) {
    viewModelScope.launch {
        val storedReaction = messageActionRepository.get(
            userId,
            react.message.channel,
            react.message.timetoken,
            react.reaction.type,
            react.reaction.value,
        )
    ...
    }
```

If such a reaction, like `thumbs up`, hasn't been previously selected by this user and is not present in the local database, it needs to be added. `messageReactionService` calls `actionService` to add the given action entry to the PubNub Message Persistence.

```kotlin
    override suspend fun add(
        channel: ChannelId,
        messageTimetoken: Long,
        type: String,
        value: String,
    ) {
        logger.i("Add message action '$type:$value' on channel '$channel'")
        try {
            val result = actionService.add(channel, PNMessageAction(type, value, messageTimetoken))
                .toResult(channel)
            addAction(result)
        } catch (e: Exception) {
            logger.e(e, "Cannot add message action")
        }
    }
```

If the action entry gets stored in PubNub successfully, `messageReactionService` saves this action information in the local repository.

```kotlin
private suspend fun addAction(result: PNMessageActionResult) {
    messageActionRepository.insertOrUpdate(mapper.map(result))
}
```

The local database holds these details about each message reaction, where `published` stands for the time when the message action was added (check `DBMessageAction.kt` for details).

```kotlin
data class DBMessageAction(
    override val channel: ChannelId,
    override val user: UserId,
    override val messageTimestamp: Timetoken,
    override val published: Timetoken,
    override val type: String,
    override val value: String,
    ...
)
```