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

Message

Customizing Messages with the Component Factory

Introduction

Every application provides a unique look and feel to their own messaging interface. In v10 (design-refresh), message customization uses a centralized component factory pattern instead of per-widget builder callbacks.

The component factory provides:

  • App-wide message customization without repeating configuration on every widget
  • A consistent, composable sub-component architecture
  • The ability to replace individual sub-components (avatar, footer, reactions, text)

Using Component Builders

The simplest way to register app-wide component builders is via the componentBuilders parameter on StreamChat:

StreamChat(
  client: client,
  componentBuilders: StreamComponentBuilders(
    extensions: streamChatComponentBuilders(
      messageWidget: (context, props) {
        return DefaultStreamMessage(
          props: props.copyWith(
            actionsBuilder: (context, defaultActions) {
              return [
                ...defaultActions,
                StreamContextMenuAction(
                  leading: const Icon(Icons.star),
                  label: const Text('Favourite'),
                  onTap: () => _favourite(props.message),
                ),
              ];
            },
          ),
        );
      },
    ),
  ),
  child: const MyHomePage(),
)

For subtree-scoped overrides (e.g., customizing builders only in one part of the app), wrap that subtree with StreamComponentFactory directly:

StreamComponentFactory(
  builders: StreamComponentBuilders(
    extensions: streamChatComponentBuilders(
      messageWidget: (context, props) => MySpecialMessage(props: props),
    ),
  ),
  child: const ChatDetailScreen(),
)

Sub-Components

DefaultStreamMessage is composed of named sub-components you can replace individually:

Sub-componentDescription
DefaultStreamMessageTop-level default renderer; composes all sub-components
StreamMessageContentBubble, attachments, text, reactions, thread replies
StreamMessageFooterUsername, timestamp, sending status, edited indicator
StreamMessageLeadingAuthor avatar
StreamMessageReactionsClustered reaction chips around the bubble
StreamMessageTextMarkdown-rendered message text
StreamMessageDeletedDeleted message placeholder
StreamMessageSendingStatusDelivery status icon

Per-List Customization

For per-list customization, use messageBuilder on StreamMessageListView. The callback now receives StreamMessageWidgetProps instead of a pre-built widget:

StreamMessageListView(
  messageBuilder: (context, message, defaultProps) {
    // Build default widget (goes through component factory)
    return StreamMessageWidget.fromProps(props: defaultProps);
  },
),

Customize props before building:

StreamMessageListView(
  messageBuilder: (context, message, defaultProps) {
    return StreamMessageWidget.fromProps(
      props: defaultProps.copyWith(
        actionsBuilder: (context, actions) => [...actions, myAction],
      ),
    );
  },
),

Or replace entirely with your own widget:

StreamMessageListView(
  messageBuilder: (context, message, defaultProps) {
    return MyCustomMessageWidget(message: message);
  },
),

Custom Reaction Icons

Configure custom reactions globally via reactionIconResolver. Extend DefaultReactionIconResolver and override only what you need:

class MyReactionIconResolver extends DefaultReactionIconResolver {
  const MyReactionIconResolver();

  @override
  Set<String> get defaultReactions => const {'like', 'love', 'celebrate'};

  @override
  String? emojiCode(String type) {
    if (type == 'celebrate') return '🎉';
    return super.emojiCode(type);
  }
}

StreamChat(
  client: client,
  streamChatConfigData: StreamChatConfigurationData(
    reactionIconResolver: const MyReactionIconResolver(),
  ),
  child: ...,
)

Custom Attachment Builders

Register custom attachment builders globally:

class LocationAttachmentBuilder extends StreamAttachmentWidgetBuilder {
  @override
  bool canHandle(
    Message message,
    Map<String, List<Attachment>> attachments,
  ) {
    final locationAttachments = attachments['location'];
    return locationAttachments != null && locationAttachments.isNotEmpty;
  }

  @override
  Widget build(
    BuildContext context,
    Message message,
    Map<String, List<Attachment>> attachments,
  ) {
    final attachment = attachments['location']!.first;
    return LocationMapWidget(
      latitude: attachment.extraData['latitude'] as double,
      longitude: attachment.extraData['longitude'] as double,
    );
  }
}

StreamChat(
  client: client,
  streamChatConfigData: StreamChatConfigurationData(
    attachmentBuilders: [
      LocationAttachmentBuilder(),
      ...defaultAttachmentBuilders,
    ],
  ),
  child: ...,
)

Theming

Message styling is split across two theme layers:

Design-system level — StreamMessageItemThemeData controls structural visibility and layout. It is part of StreamTheme (from stream_core_flutter) and can be overridden for a subtree using StreamMessageItemTheme:

StreamMessageItemTheme(
  data: StreamMessageItemThemeData(
    leadingVisibility: StreamMessageStyleVisibility(
      incoming: StreamVisibility.visible,
      outgoing: StreamVisibility.gone,
    ),
    footerVisibility: StreamMessageStyleVisibility(
      incoming: StreamVisibility.visible,
      outgoing: StreamVisibility.visible,
    ),
    incoming: StreamMessageItemStyle(
      padding: const EdgeInsets.all(4),
      backgroundColor: Colors.white,
    ),
    outgoing: StreamMessageItemStyle(
      padding: const EdgeInsets.all(4),
      backgroundColor: Colors.blue.shade50,
    ),
  ),
  child: StreamMessageListView(controller: controller),
)

Chat-specific level — StreamMessageThemeData controls text styles, colors, and link appearance for own and other messages. It is configured via StreamChatThemeData:

StreamChatThemeData(
  ownMessageTheme: StreamMessageThemeData(
    messageTextStyle: const TextStyle(color: Colors.white),
    messageBackgroundColor: Colors.blue,
  ),
  otherMessageTheme: StreamMessageThemeData(
    messageTextStyle: const TextStyle(color: Colors.black87),
    messageBackgroundColor: Colors.grey.shade200,
  ),
)

Customizing the Channel List Item via Component Factory

The component factory also handles channel list items:

StreamComponentFactory(
  builders: StreamComponentBuilders(
    extensions: streamChatComponentBuilders(
      channelListItem: (context, props) => StreamChannelListTile(
        avatar: StreamChannelAvatar(channel: props.channel),
        title: Text(props.channel.name ?? ''),
        onTap: props.onTap,
        onLongPress: props.onLongPress,
        selected: props.selected,
      ),
    ),
  ),
  child: ...,
)