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 XML UI Components for moderated messages. For how moderated messages are represented in the SDK, see the Moderation guide.
Bounced Messages
A bounced message renders as a normal message item: MessageListItemViewTypeMapper excludes bounces from the error message view type. Only the long click path differs. MessageListView recognises the message through isModerationError and calls showModeratedMessageDialog(message), which presents a dialog with the Send anyway, Edit message and Delete message options.
Selecting an option does not perform the action by itself. MessageListView forwards it to the ModeratedMessageOptionHandler you set with setModeratedMessageHandler. If you use ChannelFragment, the handler is already wired for you. When you build your own screen, set it yourself:
messageListView.setModeratedMessageHandler { message, option ->
when (option) {
SendAnyway -> messageListViewModel.onEvent(MessageListViewModel.Event.RetryMessage(message))
EditMessage -> messageComposerViewModel.performMessageAction(Edit(message))
DeleteMessage -> messageListViewModel.onEvent(MessageListViewModel.Event.DeleteMessage(message))
else -> Unit
}
}MessageListViewModel.bindView does not set this handler. If it is missing, selecting an option throws an IllegalStateException.
Replacing the Dialog
The dialog itself is internal to the SDK and its option list is fixed, so you cannot subclass it or add options to it. To use your own UI, replace the long click behaviour with setOnModeratedMessageLongClickListener and return true to signal that you handled the click:
messageListView.setOnModeratedMessageLongClickListener { message ->
MaterialAlertDialogBuilder(requireContext())
.setTitle("Message not sent")
.setMessage("This message goes against our community guidelines.")
.setPositiveButton("Edit message") { _, _ ->
messageComposerViewModel.performMessageAction(Edit(message))
}
.setNegativeButton("Delete message") { _, _ ->
messageListViewModel.onEvent(MessageListViewModel.Event.DeleteMessage(message))
}
.setNeutralButton("Cancel", null)
.show()
true
}The return value tells MessageListView whether the long click was consumed. Passing null instead of a listener removes the moderation handling entirely, so long pressing a bounced message does nothing.
If you need custom options in the moderation dialog, use the Compose UI Components, where ModeratedMessageDialog accepts a custom option list. See Message Moderation.
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.
Whether a confirmation dialog appears first depends on streamUiFlagMessageConfirmationEnabled. It is false on a bare MessageListView, but the layout behind ChannelFragment sets it to true, so screens built on the fragment confirm by default.
To hide the action, disable it in your MessageListView style:
<io.getstream.chat.android.ui.feature.messages.list.MessageListView
android:id="@+id/messageListView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:streamUiFlagMessageEnabled="false" />To flag with a reason, set a MessageFlagHandler. Route it through the view model rather than calling ChatClient.flagMessage directly, so the result still reaches handleFlagMessageResult and the error feedback keeps working:
messageListView.setMessageFlagHandler { message ->
messageListViewModel.onEvent(
MessageListViewModel.Event.FlagMessage(
message = message,
reason = "inappropriate",
customData = emptyMap(),
resultHandler = messageListView::handleFlagMessageResult,
),
)
}MessageListViewModel.bindView sets this handler itself, and the setter is a plain assignment. Set your own handler after bindView, otherwise it is silently replaced.
Two more handlers let you customize the surrounding flow without replacing the flag call itself:
setConfirmFlagMessageHandlerreplaces the confirmation dialog. Invoke theconfirmCallbackto proceed with flagging. It only runs whenstreamUiFlagMessageConfirmationEnabledistrue.setFlagMessageResultHandlerreceives theResult<Flag>of the flag call, so you can show your own success or error feedback.
messageListView.setConfirmFlagMessageHandler { message, confirmCallback ->
MaterialAlertDialogBuilder(requireContext())
.setTitle("Report message?")
.setMessage("Our moderators will review this message.")
.setPositiveButton("Report") { _, _ -> confirmCallback() }
.setNegativeButton("Cancel", null)
.show()
}For more details about message options, see MessageListView.
Changing the Texts
If you only need to change the texts, you don't have to set any handler. 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_ui_message_list_flag_message |
Flag Message |
stream_ui_message_list_flag_confirmation_title |
Flag Message |
stream_ui_message_list_flag_confirmation_message |
Do you want to send a copy of this message to a moderator for further investigation? |
stream_ui_message_list_flag_confirmation_positive_button |
Flag |
stream_ui_message_list_flag_confirmation_negative_button |
Cancel |
stream_ui_message_list_error_flag_message |
Failed to flag message |
See the localization guide for more details.