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 MessagesScreen then shows a ModeratedMessageDialog with the Send anyway, Edit message and Delete message options. Tapping an ordinary bounced message 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.inputBackground,
),
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.title3Bold,
color = ChatTheme.colors.textHighEmphasis,
)
},
dialogDescription = {
Text(
text = "This message goes against our community guidelines.",
style = ChatTheme.typography.body,
color = ChatTheme.colors.textLowEmphasis,
)
},
)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.inputBackground,
),
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.
MessagesScreen 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(
moderatedMessageItem: ModeratedMessageItemState,
) {
val message = moderatedMessageItem.message
Text(
modifier = Modifier.fillMaxWidth().padding(8.dp),
text = message.moderation?.originalText ?: message.text,
color = ChatTheme.colors.textLowEmphasis,
)
}
}
ChatTheme(
componentFactory = CustomChatComponentFactory(),
) {
MessagesScreen(viewModelFactory = viewModelFactory)
}This hook does not affect bounced messages, which stay regular message items. Customize those through the dialog above.
ChatComponentFactory is available on the Compose SDK since version 6.11.0. On earlier versions, pass a moderatedMessageContent to MessageContainer through the itemContent of MessageList.
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 options theme:
ChatTheme(
messageOptionsTheme = MessageOptionsTheme.defaultTheme(
optionVisibility = MessageOptionItemVisibility(
isFlagMessageVisible = false,
),
),
) {
MessagesScreen(viewModelFactory = viewModelFactory)
}Flagging with a reason needs your own screen, because MessagesScreen 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 Selected Message Menu.
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.