StreamMessageReactionPicker(
message: message,
onReactionPicked: (reactionType) => _addReaction(reactionType),
)Reactions
Reactions
Overview
The reactions system in v10 (design-refresh) introduces a new set of components for displaying, picking, and listing reactions. The key components are:
StreamMessageReactionPicker— the quick-pick reaction barReactionDetailSheet— a bottom sheet showing full reaction detailsStreamReactionListView+StreamReactionListController— paginated list of reactorsReactionIconResolver— abstract contract for customizing reaction icons/emoji
StreamMessageReactionPicker
StreamMessageReactionPicker (previously StreamReactionPicker) displays the quick-pick reaction bar. The available reactions come from StreamChatConfigurationData.reactionIconResolver.defaultReactions.
Visual customization is done via StreamReactionPickerTheme:
StreamReactionPickerTheme(
data: StreamReactionPickerThemeData(
backgroundColor: Colors.white,
elevation: 4,
spacing: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(24)),
),
),
child: ...,
)ReactionIconResolver
ReactionIconResolver is an abstract contract for mapping reaction types to emoji or widgets. Extend DefaultReactionIconResolver and override only what you need:
class MyReactionIconResolver extends DefaultReactionIconResolver {
const MyReactionIconResolver();
// Which reactions appear in the quick-pick bar
@override
Set<String> get defaultReactions => const {'like', 'love', 'haha', 'wow', 'sad'};
// Return a Unicode emoji for a reaction type, or null for fallback
@override
String? emojiCode(String type) {
if (type == 'celebrate') return '🎉';
return super.emojiCode(type); // delegates to streamSupportedEmojis map
}
}Register the resolver globally:
StreamChat(
client: client,
streamChatConfigData: StreamChatConfigurationData(
reactionIconResolver: const MyReactionIconResolver(),
),
child: ...,
)Custom rendering (e.g. Twemoji images):
class MyReactionIconResolver extends DefaultReactionIconResolver {
const MyReactionIconResolver();
@override
Widget resolve(BuildContext context, String type) {
switch (type) {
case 'love':
return MyTwemojiWidget(assetName: 'heart');
case 'haha':
return MyTwemojiWidget(assetName: 'joy');
default:
return super.resolve(context, type);
}
}
}ReactionDetailSheet
ReactionDetailSheet replaces MessageReactionsModal. It shows a draggable bottom sheet with:
- Total reaction count
- Filter chips per reaction type
- Paginated list of users who reacted
Use the static show method — the constructor is private:
final action = await ReactionDetailSheet.show(
context: context,
message: message,
initialReactionType: 'like', // optional: pre-select a type
);
if (action is SelectReaction) {
_handleReactionSelected(action.reaction);
}show returns MessageAction?:
SelectReaction— user picked or removed a reactionnull— sheet dismissed without selection
StreamReactionListController
StreamReactionListController loads and paginates reactions for a message. It extends PagedValueNotifier<String?, Reaction>.
final controller = StreamReactionListController(
client: StreamChat.of(context).client,
messageId: message.id,
sort: const [SortOption.desc(ReactionSortKey.createdAt)],
limit: 25,
);
await controller.doInitialLoad();Filter reactions by type at runtime (e.g. when user taps a filter chip):
controller.filter = Filter.equal('type', 'like');
controller.doInitialLoad();StreamReactionListView
StreamReactionListView renders a paginated list of reactions using a StreamReactionListController:
StreamReactionListView(
controller: controller,
itemBuilder: (context, reactions, index) {
final reaction = reactions[index];
return ListTile(
leading: Text(reaction.type),
title: Text(reaction.user?.name ?? ''),
);
},
emptyBuilder: (_) => const Center(child: Text('No reactions yet')),
)| Parameter | Required | Description |
|---|---|---|
controller | yes | Provides and paginates reaction data |
itemBuilder | yes | Builds each reaction item |
separatorBuilder | no | Builds separators between items |
emptyBuilder | no | Widget shown when there are no reactions |
loadingBuilder | no | Widget shown during initial load |
errorBuilder | no | Widget shown on error |
loadMoreTriggerIndex | no | How many items from end to trigger next page (default: 3) |