StreamMessageListView(
messageBuilder: (context, message, defaultProps) {
return StreamMessageItem.fromProps(
props: defaultProps.copyWith(
// Enable swipe-to-reply gesture
swipeToReply: true,
// Custom padding for the message bubble
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
),
);
},
)Custom Message List
This recipe shows how to replace only the message bubble layout while keeping all of StreamMessageListView's built-in behavior (pagination, reactions, threads, typing indicators).
Approach: messageBuilder with copyWith
Use the messageBuilder callback on StreamMessageListView to intercept each message and customize its props before rendering:
Approach: fully custom message widget
For a completely different layout, return your own widget from messageBuilder. Read the message from defaultProps.message and determine ownership by comparing the sender's ID against the current user:
StreamMessageListView(
messageBuilder: (context, message, defaultProps) {
final currentUserId = StreamChat.of(context).currentUser?.id;
final isOwn = message.user?.id == currentUserId;
return Align(
alignment: isOwn ? Alignment.centerRight : Alignment.centerLeft,
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: isOwn ? Colors.blue : Colors.grey.shade200,
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (!isOwn)
Text(
message.user?.name ?? '',
style: const TextStyle(fontWeight: FontWeight.bold),
),
Text(
message.text ?? '',
style: TextStyle(
color: isOwn ? Colors.white : Colors.black87,
),
),
],
),
),
);
},
)Approach: app-wide via StreamComponentFactory
To apply a custom message layout throughout the entire app without modifying each StreamMessageListView, use StreamComponentFactory:
StreamComponentFactory(
builders: StreamComponentBuilders(
extensions: streamChatComponentBuilders(
messageItem: (context, props) => MyCustomMessage(props: props),
),
),
child: MyApp(),
)Tips
- Keep system and ephemeral messages (typing indicators, deleted messages) handled by
defaultPropsto avoid breaking built-in behavior. - To check if a message belongs to the current user, compare
message.user?.id == StreamChat.of(context).currentUser?.id. - Reactions, threads, and editing are driven by
actionsBuilderonStreamMessageItemProps. Copy the default actions and add or remove items rather than replacing the list entirely.