This is beta documentation for Stream Chat Flutter SDK v10. For the latest stable version, see the latest version (v9).

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 bar
  • ReactionDetailSheet — a bottom sheet showing full reaction details
  • StreamReactionListView + StreamReactionListController — paginated list of reactors
  • ReactionIconResolver — 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.

StreamMessageReactionPicker(
  message: message,
  onReactionPicked: (reactionType) => _addReaction(reactionType),
)

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 reaction
  • null — 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')),
)
ParameterRequiredDescription
controlleryesProvides and paginates reaction data
itemBuilderyesBuilds each reaction item
separatorBuildernoBuilds separators between items
emptyBuildernoWidget shown when there are no reactions
loadingBuildernoWidget shown during initial load
errorBuildernoWidget shown on error
loadMoreTriggerIndexnoHow many items from end to trigger next page (default: 3)