Message Moderation

Stream Chat can moderate messages before they are delivered (bounce) and lets users report messages to moderators (flag). This page shows how to customize the Compose components for moderated messages. For how moderated messages are represented in the SDK, see the Moderation guide.

Bounced Messages

A bounced message stays in the MessageList as a regular message item. Long pressing it sets the selected message state to SelectedMessageFailedModerationState, and ChannelScreen then shows a ModeratedMessageDialog with the Send anyway, Edit message and Delete message options. Tapping it does nothing; the dialog opens on long press only.

The options are ModeratedMessageOption instances:

Option Result
SendAnyway Sends the message again through MessageListViewModel.performMessageAction(Resend(message)).
EditMessage Puts the message into the composer through MessageComposerViewModel.performMessageAction(Edit(message)).
DeleteMessage Deletes the local copy through MessageListViewModel.deleteMessage(message, hard = true).
CustomModerationOption Your own option. The SDK passes it to your handler and does nothing else.

Customizing the Dialog

ModeratedMessageDialog is a public composable with slots for its title, description and options, so you can change any part of it. Pass a modifier that paints a background: the dialog content is a bare Column, so without one it renders transparently over the scrim.

ModeratedMessageDialog(
    message = message,
    modifier = Modifier.background(
        shape = MaterialTheme.shapes.medium,
        color = ChatTheme.colors.backgroundCoreSurfaceDefault,
    ),
    onDismissRequest = { listViewModel.removeOverlay() },
    onDialogOptionInteraction = { message, option -> /* Handle the option */ },
    // Do not allow sending a bounced message anyway.
    moderatedMessageOptions = listOf(EditMessage, DeleteMessage),
    dialogTitle = {
        Text(
            text = "Message not sent",
            style = ChatTheme.typography.headingMedium,
            color = ChatTheme.colors.textPrimary,
        )
    },
    dialogDescription = {
        Text(
            text = "This message goes against our community guidelines.",
            style = ChatTheme.typography.bodyDefault,
            color = ChatTheme.colors.textSecondary,
        )
    },
)

To add your own option, use CustomModerationOption and handle it in onDialogOptionInteraction:

val reportOption = CustomModerationOption(text = R.string.report_to_support)

ModeratedMessageDialog(
    message = message,
    modifier = Modifier.background(
        shape = MaterialTheme.shapes.medium,
        color = ChatTheme.colors.backgroundCoreSurfaceDefault,
    ),
    onDismissRequest = { listViewModel.removeOverlay() },
    moderatedMessageOptions = listOf(SendAnyway, EditMessage, DeleteMessage, reportOption),
    onDialogOptionInteraction = { message, option ->
        when (option) {
            is SendAnyway -> listViewModel.performMessageAction(Resend(message))
            is EditMessage -> composerViewModel.performMessageAction(Edit(message))
            is DeleteMessage -> listViewModel.deleteMessage(message = message, hard = true)
            is CustomModerationOption -> reportToSupport(message)
        }
    },
)

If you only need to change how a single option row looks, pass an itemContent to ModeratedMessageDialogOptions, which is the composable behind the dialogOptions slot. ModeratedMessageOptionItem renders the default row.

Note:

ChannelScreen builds the dialog itself and does not expose it as a slot. To use a customized ModeratedMessageDialog, build your own screen around MessageList and render the dialog when listViewModel.currentMessagesState.value.selectedMessageState is a SelectedMessageFailedModerationState. See Custom Message List.

Blocked and Removed Messages

Moderation can also block or remove a message outright instead of bouncing it. MessageListController turns every error message that is not a bounce into a ModeratedMessageItemState, which the list renders through ChatComponentFactory.MessageListModeratedItemContent rather than as a regular message:

class CustomChatComponentFactory : ChatComponentFactory {

    @Composable
    override fun LazyItemScope.MessageListModeratedItemContent(
        params: MessageListModeratedItemContentParams,
    ) {
        val message = params.moderatedMessageItem.message
        Text(
            modifier = Modifier.fillMaxWidth().padding(8.dp),
            text = message.moderation?.originalText ?: message.text,
            color = ChatTheme.colors.textSecondary,
        )
    }
}

ChatTheme(
    componentFactory = CustomChatComponentFactory(),
) {
    ChannelScreen(viewModelFactory = viewModelFactory)
}
Note:

This hook does not affect bounced messages, which stay regular message items. Customize those through the dialog above.

Flag Action

The Flag message action is added to the message options overlay for messages from other users, when the user has the flag-message channel capability. Selecting it shows a confirmation dialog and then flags the message without a reason.

To hide the action, set isFlagMessageVisible to false in the message actions configuration:

ChatTheme(
    config = ChatUiConfig(
        messageActions = MessageActionsConfig(
            optionsVisibility = MessageActionsOptionsVisibility(
                isFlagMessageVisible = false,
            ),
        ),
    ),
) {
    ChannelScreen(viewModelFactory = viewModelFactory)
}

Flagging with a reason needs your own screen, because ChannelScreen always shows its own confirmation dialog and then calls flagMessage with reason = null. On a custom screen, handle the Flag action and call the view model, which accepts a reason:

listViewModel.flagMessage(
    message = message,
    reason = "inappropriate",
    customData = emptyMap(),
)

For more details about message options, see Message Actions.

Changing the Texts

If you only need to change the texts, you don't have to override any component. Redefine the following keys in your app's strings.xml:

Key Default value
stream_ui_moderation_dialog_title Failed to send message
stream_ui_moderation_dialog_description This message might violate our moderation policy. Are you sure you want to send it? Please take a look at our Community Guidelines.
stream_ui_moderation_dialog_send Send anyway
stream_ui_moderation_dialog_edit Edit message
stream_ui_moderation_dialog_delete Delete message
stream_compose_flag_message Flag Message
stream_compose_flag_message_title Flag Message
stream_compose_flag_message_text Do you want to send a copy of this message to a moderator for further investigation?

See the localization guide for more details.

Add Chat to my app: getstream.io/SKILL.md

The fastest way to build with Stream. Start a new project or improve an existing one. Full CLI and documentation integration out of the box.


Ask your agent:

/stream Build me a Social App with Feeds and Moderation.
/stream Any livestream calls running?
/stream Chat Android v7: <Your Question>