StreamChatTheme(
data: StreamChatThemeData(
colorTheme: StreamColorTheme.light(
accentPrimary: Colors.blue,
),
),
child: MaterialApp(...),
)v10.0
Stream Chat Flutter SDK v10.0.0 Migration Guide
This guide covers all breaking changes in Stream Chat Flutter SDK v10.0.0. Whether you're upgrading from v9.x or from a v10 beta, this document provides the complete migration path.
Table of Contents
- Who Should Read This
- Quick Reference
- Theming
- Channel List Item
- Message Widget
- Message Actions
- Reactions
- Avatars
- Message Composer
- Attachment Picker
- Image CDN
- Unread Indicator
- Audio
- Icons & Headers
- Localizations
- Attachments & Polls
- Message State & Deletion
- File Upload
- onAttachmentTap
- Appendix: Beta Release Timeline
- Migration Checklist
Who Should Read This
| Upgrading From | Sections to Review |
|---|---|
| v9.x | All sections |
| v10.0.0-beta.1 | All sections introduced after beta.1 |
| v10.0.0-beta.3 | Sections introduced in beta.4 and later |
| v10.0.0-beta.4 | Sections introduced in beta.7 and later |
| v10.0.0-beta.7 | Sections introduced in beta.8 and later |
| v10.0.0-beta.8 | Sections introduced in beta.9 and later |
| v10.0.0-beta.9 | Sections introduced in beta.12 and later |
| v10.0.0-beta.12 | Sections introduced in beta.13 (design refresh) |
| v10.0.0-beta.13 | No additional changes |
Each breaking change section includes an "Introduced in" tag so you can quickly identify which changes apply to your upgrade path.
Quick Reference
| Feature Area | Key Changes |
|---|---|
| Theming | StreamChatTheme wrapper replaced by StreamTheme extension on MaterialApp.theme |
| Channel List | StreamChannelListTile → StreamChannelListItem, theme class renames |
| Message Widget | Props-based API via StreamMessageWidgetProps, removed show* booleans and builder callbacks |
| Message Actions | StreamContextMenuAction<T> replaces StreamMessageAction, actionsBuilder replaces customActions |
| Reactions | Reaction object API, StreamMessageReactionPicker, reactionIconResolver, ReactionDetailSheet |
| Avatars | Size enums replace BoxConstraints, StreamGroupAvatar → StreamUserAvatarGroup |
| Message Composer | hideSendAsDm → canAlsoSendToChannelFromThread (inverted), new StreamChatMessageComposer |
| Attachment Picker | Sealed class hierarchy (AttachmentsPicked, PollCreated, AttachmentPickerError), builder pattern for options, typed errors |
| Image CDN | getResizedImageUrl → StreamImageCDN.resolveUrl(), ResizeMode/CropMode enums |
| Unread Indicator | Named constructors, updated UnreadIndicatorButton callbacks |
| Audio | Moved to stream_core_flutter, theming via StreamTheme |
| Icons & Headers | StreamSvgIcon deprecated → Icon(context.streamIcons.*), header default changes |
| Message State | MessageDeleteScope replaces bool hard, delete-for-me support |
| File Upload | Four new abstract methods on AttachmentFileUploader |
| onAttachmentTap | New FutureOr<bool> signature with BuildContext and fallback support |
Theming
Introduced in: v10.0.0-beta.13
StreamChatTheme → StreamTheme via MaterialApp extensions
The StreamChatTheme wrapper widget has been replaced by a StreamTheme extension registered on MaterialApp.theme.
Before:
After:
MaterialApp(
theme: ThemeData(
extensions: [
StreamTheme(
brightness: Brightness.light,
colorScheme: StreamColorScheme.light().copyWith(
primary: Colors.blue,
),
),
],
),
home: StreamChat(client: client, child: ...),
)Use convenience factories StreamTheme.light() or StreamTheme.dark() as a starting point:
MaterialApp(
theme: ThemeData(extensions: [StreamTheme.light()]),
darkTheme: ThemeData(
brightness: Brightness.dark,
extensions: [StreamTheme.dark()],
),
home: ...,
)If no StreamTheme is provided, a default theme is automatically derived from Theme.of(context).brightness.
Channel List Item
Introduced in: v10.0.0-beta.13
StreamChannelListTile → StreamChannelListItem
StreamChannelListTile slot parameters (leading, title, subtitle, trailing) have been removed. The new StreamChannelListItem takes only interaction props; slot customization moves to StreamComponentFactory.
Before:
StreamChannelListTile(
channel: channel,
onTap: () => openChannel(channel),
tileColor: Colors.white,
selectedTileColor: Colors.blue.shade50,
selected: isSelected,
leading: StreamChannelAvatar(channel: channel),
title: StreamChannelName(channel: channel),
)After:
StreamChannelListItem(
channel: channel,
onTap: () => openChannel(channel),
selected: isSelected,
)To customize slots, use StreamComponentFactory:
StreamComponentFactory(
builders: StreamComponentBuilders(
extensions: streamChatComponentBuilders(
channelListItem: (context, props) => StreamChannelListTile(
avatar: StreamChannelAvatar(channel: props.channel),
title: Text(props.channel.name ?? ''),
onTap: props.onTap,
selected: props.selected,
),
),
),
child: ...,
)Theme: StreamChannelPreviewThemeData → StreamChannelListItemThemeData
| Old | New |
|---|---|
StreamChatThemeData.channelPreviewTheme | StreamChatThemeData.channelListItemTheme |
StreamChannelPreviewThemeData | StreamChannelListItemThemeData |
StreamChannelPreviewTheme.of(context) | StreamChannelListItemTheme.of(context) |
lastMessageAtStyle | timestampStyle |
lastMessageAtFormatter (theme prop) | ChannelLastMessageDate(formatter: ...) widget param |
tileColor / selectedTileColor | backgroundColor: WidgetStateProperty<Color?> |
Before:
StreamChatThemeData(
channelPreviewTheme: StreamChannelPreviewThemeData(
titleStyle: TextStyle(fontWeight: FontWeight.bold),
lastMessageAtStyle: TextStyle(fontSize: 12),
unreadCounterColor: Colors.red,
),
)After:
StreamChatThemeData(
channelListItemTheme: StreamChannelListItemThemeData(
titleStyle: const TextStyle(fontWeight: FontWeight.bold),
timestampStyle: const TextStyle(fontSize: 12),
// unreadCounterColor → use StreamBadgeNotificationThemeData
),
)State-dependent colors:
StreamChannelListItemThemeData(
backgroundColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) return Colors.blue.shade50;
return Colors.white;
}),
)Message Widget
Introduced in: v10.0.0-beta.13
Props-based API
The old 50+ parameter StreamMessageWidget is now a thin shell. Configuration moves to StreamMessageWidgetProps with copyWith().
Before (messageBuilder with copyWith on widget):
StreamMessageListView(
messageBuilder: (context, details, messages, defaultWidget) {
return defaultWidget.copyWith(showReactions: false);
},
)After (messageBuilder with copyWith on props):
StreamMessageListView(
messageBuilder: (context, message, defaultProps) {
return StreamMessageWidget.fromProps(
props: defaultProps.copyWith(
actionsBuilder: (context, actions) => actions,
),
);
},
)Removed Visibility Booleans
All show* boolean parameters have been removed. Visibility is now controlled via StreamMessageItemThemeData and channel permissions:
| Old Parameter | Migration Path |
|---|---|
showReactions | StreamMessageItemThemeData visibility |
showDeleteMessage / showEditMessage | Channel permissions |
showUsername / showTimestamp / showSendingIndicator | StreamMessageItemThemeData.footerVisibility |
showUserAvatar | StreamMessageItemThemeData.leadingVisibility |
showThreadReplyIndicator | Shown automatically when replyCount > 0 |
showReactionTail | Tail is shown automatically when the picker is visible |
Removed Builder Callbacks
| Old Parameter | Migration Path |
|---|---|
userAvatarBuilder | Component factory (replace DefaultStreamMessage) |
textBuilder | Component factory (replace StreamMessageContent) |
quotedMessageBuilder | Component factory (replace StreamMessageContent) |
deletedMessageBuilder | Component factory (replace StreamMessageContent) |
reactionPickerBuilder | StreamChatConfigurationData.reactionIconResolver |
customActions | actionsBuilder on StreamMessageWidgetProps |
onCustomActionTap | onTap per StreamContextMenuAction |
Changed Callback Signatures
| Old | New |
|---|---|
onLinkTap: void Function(String url) | onMessageLinkTap: void Function(Message, String) |
onMentionTap: void Function(User) | onUserMentionTap: void Function(User) |
onQuotedMessageTap: void Function(String?) | onQuotedMessageTap: void Function(Message) |
Typedef Changes
| Old | New |
|---|---|
MessageBuilder = Widget Function(BuildContext, MessageDetails, List<Message>, StreamMessageWidget) | StreamMessageWidgetBuilder = Widget Function(BuildContext, Message, StreamMessageWidgetProps) |
ParentMessageBuilder | StreamMessageWidgetBuilder |
StreamDeletedMessage | StreamMessageDeleted |
Theme: Automatic Resolution
The messageTheme parameter has been removed. Theme is resolved automatically from context:
// Before
StreamMessageWidget(message: message, messageTheme: streamTheme.ownMessageTheme)
// After
StreamMessageWidget(message: message)Message Actions
Introduced in: v10.0.0-beta.13
StreamMessageAction → StreamContextMenuAction<T>
StreamMessageAction (data class) and StreamMessageActionItem (render widget) have been merged into StreamContextMenuAction<T>, a self-rendering widget.
Property mapping:
StreamMessageAction | StreamContextMenuAction |
|---|---|
action: T | value: T? |
title: Widget? | label: Widget (now required) |
leading: Widget? | leading: Widget? |
isDestructive: bool | isDestructive: bool or .destructive constructor |
Before:
StreamMessageAction(
action: CopyMessage(message: message),
leading: const StreamSvgIcon(icon: StreamSvgIcons.copy),
title: Text('Copy'),
)After:
StreamContextMenuAction<MessageAction>(
value: CopyMessage(message: message),
leading: Icon(context.streamIcons.copy),
label: const Text('Copy'),
onTap: () => _copyMessage(message),
)customActions + onCustomActionTap → actionsBuilder
Before:
StreamMessageWidget(
message: message,
customActions: [
StreamMessageAction(
action: CustomMessageAction(message: message),
leading: const Icon(Icons.star),
title: const Text('Favourite'),
),
],
onCustomActionTap: (action) => _favourite(action.message),
)After:
StreamMessageWidget(
message: message,
actionsBuilder: (context, defaultActions) => [
...defaultActions,
StreamContextMenuAction(
leading: const Icon(Icons.star),
label: const Text('Favourite'),
onTap: () => _favourite(message),
),
],
)showStreamDialog
Replace showDialog with showStreamDialog when presenting Stream modals:
final action = await showStreamDialog<MessageAction>(
context: context,
builder: (_) => StreamMessageActionsModal(...),
);Reactions
The reaction system has been updated with a unified Reaction object API, renamed widgets, and a resolver-based icon system.
SendReaction
Introduced in: v10.0.0-beta.4
Key Changes
sendReactionmethod now accepts a fullReactionobject instead of individual parameters.
Migration Steps
Before:
channel.sendReaction(
message,
'like',
score: 1,
extraData: {'custom_field': 'value'},
);
client.sendReaction(
messageId,
'love',
enforceUnique: true,
extraData: {'custom_field': 'value'},
);After:
channel.sendReaction(
message,
Reaction(
type: 'like',
score: 1,
emojiCode: '👍',
extraData: {'custom_field': 'value'},
),
);
client.sendReaction(
messageId,
Reaction(
type: 'love',
emojiCode: '❤️',
extraData: {'custom_field': 'value'},
),
enforceUnique: true,
);Important:
- The
sendReactionmethod now requires aReactionobject- Optional parameters like
enforceUniqueandskipPushremain as method parameters- You can now specify custom emoji codes for reactions using the
emojiCodefield
StreamMessageReactionPicker
Introduced in: v10.0.0-beta.13 (renamed from
StreamReactionPicker)
Key Changes
StreamReactionPickerhas been renamed toStreamMessageReactionPickeronReactionPickedis required
Migration Steps
Before:
StreamReactionPicker(message: message)After:
StreamMessageReactionPicker(
message: message,
onReactionPicked: onReactionPicked,
)reactionIcons → reactionIconResolver
Introduced in: v10.0.0-beta.13
Migration Steps
Before:
StreamChatConfigurationData(
reactionIcons: [/* list of icon builders */],
)After:
StreamChatConfigurationData(
reactionIconResolver: const MyReactionIconResolver(),
)Extend DefaultReactionIconResolver to customize the available reactions:
class MyReactionIconResolver extends DefaultReactionIconResolver {
const MyReactionIconResolver();
@override
Set<String> get defaultReactions => const {'like', 'love', 'haha', 'wow', 'sad'};
}MessageReactionsModal → ReactionDetailSheet
Introduced in: v10.0.0-beta.13
Migration Steps
Before:
showDialog(
context: context,
builder: (_) => MessageReactionsModal(message: message),
)After:
await ReactionDetailSheet.show(context: context, message: message)Important: Await the
ReactionDetailSheet.show()call to get the result instead of providing anonReactionPickedcallback.
ReactionPickerIconList
Introduced in: v10.0.0-beta.9
Key Changes
messageparameter has been removedreactionIconstype changed fromList<StreamReactionIcon>toList<ReactionPickerIcon>onReactionPickedcallback renamed toonIconPickedwith new signature:ValueSetter<ReactionPickerIcon>iconBuilderparameter changed from default value to nullable with internal fallback- Message-specific logic (checking for own reactions) moved to parent widget
Migration Steps
Before:
ReactionPickerIconList(
message: message,
reactionIcons: icons,
onReactionPicked: (reaction) {
channel.sendReaction(message, reaction);
},
)After:
// Map StreamReactionIcon to ReactionPickerIcon with selection state
final ownReactions = [...?message.ownReactions];
final ownReactionsMap = {for (final it in ownReactions) it.type: it};
final pickerIcons = icons.map((icon) {
return ReactionPickerIcon(
type: icon.type,
builder: icon.builder,
isSelected: ownReactionsMap[icon.type] != null,
);
}).toList();
ReactionPickerIconList(
reactionIcons: pickerIcons,
onIconPicked: (pickerIcon) {
final reaction = ownReactionsMap[pickerIcon.type] ??
Reaction(type: pickerIcon.type);
channel.sendReaction(message, reaction);
},
)Important:
- This is typically an internal widget used by
StreamMessageReactionPicker- If you were using it directly, you now need to handle reaction selection state externally
- Use
StreamMessageReactionPickerfor most use cases instead ofReactionPickerIconList
Avatars
Introduced in: v10.0.0-beta.13
constraints → size enum
All avatar components now use size enums instead of BoxConstraints.
Before:
StreamUserAvatar(
user: user,
constraints: BoxConstraints.tight(const Size(40, 40)),
showOnlineStatus: true,
onTap: (user) => showProfile(user),
)After:
GestureDetector(
onTap: () => showProfile(user),
child: StreamUserAvatar(
user: user,
size: StreamAvatarSize.lg, // 40px
showOnlineIndicator: true,
),
)Important:
showOnlineStatusis renamed toshowOnlineIndicatoronTaphas been removed from avatar widgets — wrap withGestureDetectororInkWellinstead
StreamGroupAvatar → StreamUserAvatarGroup
// Before
StreamGroupAvatar(
channel: channel,
members: otherMembers,
constraints: BoxConstraints.tight(const Size(40, 40)),
)
// After
StreamUserAvatarGroup(
users: otherMembers.map((m) => m.user!),
size: StreamAvatarGroupSize.lg,
)New: StreamUserAvatarStack
For overlapping avatar stacks (e.g. thread participants):
StreamUserAvatarStack(
users: threadParticipants,
size: StreamAvatarStackSize.xs,
max: 3,
)Message Composer
Introduced in: v10.0.0-beta.13
hideSendAsDm → canAlsoSendToChannelFromThread (logic inverted)
| Old | New |
|---|---|
hideSendAsDm: true | canAlsoSendToChannelFromThread: false |
hideSendAsDm: false | canAlsoSendToChannelFromThread: true (new default) |
// Before
StreamMessageInput(hideSendAsDm: true)
// After
StreamMessageInput(canAlsoSendToChannelFromThread: false)New: StreamChatMessageComposer
StreamChatMessageComposer is a pure UI composer with no business logic. Use it when you want the new design system visuals with custom send logic. StreamMessageInput remains the recommended choice for out-of-the-box functionality.
Attachment Picker
The attachment picker system has been redesigned with a sealed class hierarchy, improved type safety, and a flexible builder pattern for customization.
AttachmentPickerType
Introduced in: v10.0.0-beta.3
Key Changes
AttachmentPickerTypeenum replaced with sealed class hierarchy- Now supports extensible custom types like contact and location pickers
- Use built-in types like
AttachmentPickerType.imagesor define your own viaCustomAttachmentPickerType
Migration Steps
Before:
// Using enum-based attachment types
final attachmentType = AttachmentPickerType.images;After:
// Using sealed class attachment types
final attachmentType = AttachmentPickerType.images;
// For custom types
class LocationAttachmentPickerType extends CustomAttachmentPickerType {
const LocationAttachmentPickerType();
}Important: The enum is now a sealed class, but the basic usage remains the same for built-in types.
StreamAttachmentPickerOption
Introduced in: v10.0.0-beta.3
Key Changes
StreamAttachmentPickerOptionreplaced with two sealed classes:SystemAttachmentPickerOptionfor system pickers (camera, files)TabbedAttachmentPickerOptionfor tabbed pickers (gallery, polls, location)
Migration Steps
Before:
final option = AttachmentPickerOption(
title: 'Gallery',
icon: Icon(Icons.photo_library),
supportedTypes: [AttachmentPickerType.images, AttachmentPickerType.videos],
optionViewBuilder: (context, controller) {
return GalleryPickerView(controller: controller);
},
);
final webOrDesktopOption = WebOrDesktopAttachmentPickerOption(
title: 'File Upload',
icon: Icon(Icons.upload_file),
type: AttachmentPickerType.files,
);After:
// For custom UI pickers (gallery, polls)
final tabbedOption = TabbedAttachmentPickerOption(
title: 'Gallery',
icon: Icon(Icons.photo_library),
supportedTypes: [AttachmentPickerType.images, AttachmentPickerType.videos],
optionViewBuilder: (context, controller) {
return GalleryPickerView(controller: controller);
},
);
// For system pickers (camera, file dialogs)
final systemOption = SystemAttachmentPickerOption(
title: 'Camera',
icon: Icon(Icons.camera_alt),
supportedTypes: [AttachmentPickerType.images],
onTap: (context, controller) => pickFromCamera(),
);Important:
- Use
SystemAttachmentPickerOptionfor system pickers (camera, file dialogs)- Use
TabbedAttachmentPickerOptionfor custom UI pickers (gallery, polls)
showStreamAttachmentPickerModalBottomSheet
Introduced in: v10.0.0-beta.3
Key Changes
- Now returns
StreamAttachmentPickerResultinstead ofAttachmentPickerValue - Result cases updated in beta.13:
StreamAttachmentPickerSuccessandStreamAttachmentPickerCancelled
Migration Steps
Before:
final result = await showStreamAttachmentPickerModalBottomSheet(
context: context,
controller: controller,
);
// result is AttachmentPickerValueAfter:
final result = await showStreamAttachmentPickerModalBottomSheet(
context: context,
controller: controller,
);
// result is StreamAttachmentPickerResult
switch (result) {
case AttachmentsPicked(:final attachments):
// Handle picked attachments
case PollCreated(:final poll):
// Handle created poll
case AttachmentPickerError(:final error):
// Handle error
case CustomAttachmentPickerResult():
// Handle custom result
}Important: Always handle the
StreamAttachmentPickerResultreturn type with proper switch cases.
AttachmentPickerBottomSheet
Introduced in: v10.0.0-beta.3
Key Changes
StreamMobileAttachmentPickerBottomSheet→StreamTabbedAttachmentPickerBottomSheetStreamWebOrDesktopAttachmentPickerBottomSheet→StreamSystemAttachmentPickerBottomSheet
Migration Steps
Before:
StreamMobileAttachmentPickerBottomSheet(
context: context,
controller: controller,
customOptions: [option],
);
StreamWebOrDesktopAttachmentPickerBottomSheet(
context: context,
controller: controller,
customOptions: [option],
);After:
StreamTabbedAttachmentPickerBottomSheet(
context: context,
controller: controller,
customOptions: [tabbedOption],
);
StreamSystemAttachmentPickerBottomSheet(
context: context,
controller: controller,
customOptions: [systemOption],
);Important: The new names better reflect their respective layouts and functionality.
customAttachmentPickerOptions
Introduced in: v10.0.0-beta.8
Key Changes
customAttachmentPickerOptionshas been removed. UseattachmentPickerOptionsBuilderinstead.- New builder pattern provides access to default options which can be modified, reordered, or extended.
Migration Steps
Before:
StreamMessageInput(
customAttachmentPickerOptions: [
TabbedAttachmentPickerOption(
key: 'custom-location',
icon: const Icon(Icons.location_on),
supportedTypes: [AttachmentPickerType.images],
optionViewBuilder: (context, controller) {
return CustomLocationPicker();
},
),
],
)After:
StreamMessageInput(
attachmentPickerOptionsBuilder: (context, defaultOptions) {
return [
...defaultOptions,
TabbedAttachmentPickerOption(
key: 'custom-location',
icon: const Icon(Icons.location_on),
supportedTypes: [AttachmentPickerType.images],
optionViewBuilder: (context, controller) {
return CustomLocationPicker();
},
),
];
},
)Example: Filtering default options
StreamMessageInput(
attachmentPickerOptionsBuilder: (context, defaultOptions) {
// Remove poll option
return defaultOptions.where((option) => option.key != 'poll').toList();
},
)Example: Reordering options
StreamMessageInput(
attachmentPickerOptionsBuilder: (context, defaultOptions) {
return defaultOptions.reversed.toList();
},
)Using with showStreamAttachmentPickerModalBottomSheet:
final result = await showStreamAttachmentPickerModalBottomSheet(
context: context,
attachmentPickerOptionsBuilder: (context, defaultOptions) {
return [
...defaultOptions,
TabbedAttachmentPickerOption(
key: 'custom-option',
icon: const Icon(Icons.star),
supportedTypes: [AttachmentPickerType.images],
optionViewBuilder: (context, controller) {
return CustomPickerView();
},
),
];
},
);Important:
- The builder pattern gives you access to default options, allowing more flexible customization
- The builder works with both mobile (tabbed) and desktop (system) pickers
onCustomAttachmentPickerResult
Introduced in: v10.0.0-beta.8
Key Changes
onCustomAttachmentPickerResulthas been removed. UseonAttachmentPickerResultwhich returnsFutureOr<bool>.- Result handler can now short-circuit default behavior by returning
true.
Migration Steps
Before:
StreamMessageInput(
onCustomAttachmentPickerResult: (result) {
if (result is CustomAttachmentPickerResult) {
final data = result.data;
// Handle custom result
}
},
)After:
StreamMessageInput(
onAttachmentPickerResult: (result) {
if (result is CustomAttachmentPickerResult) {
final data = result.data;
// Handle custom result
return true; // Indicate we handled it - skips default processing
}
return false; // Let default handler process other result types
},
)Important:
onAttachmentPickerResultreplacesonCustomAttachmentPickerResultand must return a boolean- Return
truefromonAttachmentPickerResultto skip default handling- Return
falseto allow the default handler to process the result
StreamAttachmentPickerController
Introduced in: v10.0.0-beta.12
Key Changes
- Replaced
ArgumentError('The size of the attachment is...')withAttachmentTooLargeError. - Replaced
ArgumentError('The maximum number of attachments is...')withAttachmentLimitReachedError.
Migration Steps
Before:
try {
await controller.addAttachment(attachment);
} on ArgumentError catch (e) {
showError(e.message);
}After:
try {
await controller.addAttachment(attachment);
} on AttachmentTooLargeError catch (e) {
showError('File is too large. Max size is ${e.maxSize} bytes.');
} on AttachmentLimitReachedError catch (e) {
showError('Cannot add more attachments. Maximum is ${e.maxCount}.');
}Important:
- Replace
ArgumentErrorcatches with the specific typed errorsAttachmentTooLargeErrorprovidesfileSizeandmaxSizepropertiesAttachmentLimitReachedErrorprovidesmaxCountproperty
Image CDN
Introduced in: v10.0.0-beta.13
getResizedImageUrl → StreamImageCDN.resolveUrl
Before:
final url = imageUrl.getResizedImageUrl(
width: 200,
height: 300,
resize: 'clip',
crop: 'center',
);After:
const imageCDN = StreamImageCDN();
final url = imageCDN.resolveUrl(
imageUrl,
resize: ImageResize(
width: 200,
height: 300,
mode: ResizeMode.clip,
crop: CropMode.center,
),
);
final cacheKey = imageCDN.cacheKey(url); // stable key for CachedNetworkImageFor custom CDN support, extend StreamImageCDN and inject via StreamChatConfigurationData.imageCDN.
Attachment Thumbnail Parameters
The three separate thumbnail params on attachment widgets are replaced by a single resize parameter:
// Before
StreamImageAttachmentThumbnail(
image: attachment,
thumbnailSize: const Size(200, 300),
thumbnailResizeType: 'clip',
thumbnailCropType: 'center',
)
// After
StreamImageAttachmentThumbnail(
image: attachment,
resize: ImageResize(width: 200, height: 300, mode: ResizeMode.clip, crop: CropMode.center),
)Unread Indicator
Introduced in: v10.0.0-beta.13
StreamUnreadIndicator
Styling params (backgroundColor, textColor, textStyle) removed — styling is now controlled via StreamTheme.
Named constructors added:
StreamUnreadIndicator() // total unread messages
StreamUnreadIndicator.channels() // unread channels
StreamUnreadIndicator.threads() // unread threadsUnreadIndicatorButton
Callback signatures changed:
// Before
UnreadIndicatorButton(
onTap: () => _scrollToUnread(),
onDismiss: () => _markAllRead(),
)
// After
UnreadIndicatorButton(
onTap: (lastReadMessageId) async => _scrollToUnread(lastReadMessageId),
onDismissTap: () async => _markAllRead(),
)Audio
Introduced in: v10.0.0-beta.13
StreamAudioWaveform and StreamAudioWaveformSlider have moved to stream_core_flutter but are re-exported via stream_chat_flutter, so import paths are unchanged.
Audio waveform theming moves from StreamChatThemeData.audioWaveformTheme to StreamTheme (via MaterialApp.theme.extensions).
// Before
StreamChatThemeData(
audioWaveformTheme: StreamAudioWaveformThemeData(waveColor: Colors.blue),
)
// After
MaterialApp(
theme: ThemeData(
extensions: [
StreamTheme(brightness: Brightness.light, /* audio waveform props in StreamTheme */),
],
),
)Icons & Headers
Introduced in: v10.0.0-beta.13
StreamSvgIcon Deprecated
StreamSvgIcon and StreamSvgIcons are now @Deprecated. Replace all usages with the standard Flutter Icon widget and the new StreamIcons token set accessed via context.streamIcons.
Before:
StreamSvgIcon(icon: StreamSvgIcons.reply)
StreamSvgIcon(icon: StreamSvgIcons.copy, color: Colors.red, size: 24)After:
Icon(context.streamIcons.reply20)
Icon(context.streamIcons.copy20, color: Colors.red, size: 24)context.streamIcons reads from the nearest StreamTheme in the widget tree.
Header Widget Defaults
StreamChannelHeader, StreamChannelListHeader, and StreamThreadHeader received default-value changes:
| Parameter | Old default | New default |
|---|---|---|
centerTitle | null (platform-adaptive) | true |
elevation | 1 | 0 |
scrolledUnderElevation | — | 0 (new) |
// Restore old Android-style left-aligned titles:
StreamChannelHeader(centerTitle: false)
// Restore the elevation shadow:
StreamChannelHeader(elevation: 1)StreamChat.componentBuilders
StreamChat now accepts an optional componentBuilders parameter that automatically inserts a StreamComponentFactory into the widget tree:
Before:
StreamComponentFactory(
builders: StreamComponentBuilders(
extensions: streamChatComponentBuilders(
messageWidget: (context, props) => MyMessage(props: props),
),
),
child: StreamChat(client: client, child: MyApp()),
)After:
StreamChat(
client: client,
componentBuilders: StreamComponentBuilders(
extensions: streamChatComponentBuilders(
messageWidget: (context, props) => MyMessage(props: props),
),
),
child: MyApp(),
)StreamChatConfigurationData New Fields
Three new optional fields have been added:
| Field | Type | Default | Description |
|---|---|---|---|
attachmentBuilders | List<StreamAttachmentWidgetBuilder>? | null | Custom attachment widget builders, prepended to built-in builders |
reactionType | StreamReactionsType? | null | Controls visual style of the reactions display |
reactionPosition | StreamReactionsPosition? | null | Controls where reactions appear relative to the message bubble |
Note: The
imageCDNfield was also added — see the Image CDN section.
Attachments & Polls
Introduced in: v10.0.0-beta.13
Attachment Widget Changes
All attachment widgets now follow a Props + Component Factory pattern. The public constructor API is largely unchanged, but several parameters have been removed or consolidated.
Changes that apply to all attachment widgets:
shapeparameter removedconstraintschanged from required to optional
Per-widget changes:
| Widget | Additional Changes |
|---|---|
StreamImageAttachment | imageThumbnailSize/imageThumbnailResizeType/imageThumbnailCropType → single ImageResize? resize |
StreamFileAttachment | backgroundColor removed |
StreamUrlAttachment | Renamed to StreamLinkPreviewAttachment; messageTheme and hostDisplayName removed |
StreamUrlAttachment → StreamLinkPreviewAttachment
Before:
StreamUrlAttachment(
message: message,
urlAttachment: attachment,
messageTheme: theme.ownMessageTheme,
hostDisplayName: 'GitHub',
)After:
StreamLinkPreviewAttachment(
message: message,
urlAttachment: attachment,
)The corresponding builder was also renamed from UrlAttachmentBuilder to LinkPreviewAttachmentBuilder.
Attachment Builders
All builders have had their shape and padding parameters removed. If you subclass any attachment builder, update to use the new Props-based attachment constructors.
StreamPollInteractorThemeData
The theme has been fully redesigned with a structured theme using design system tokens:
| Removed Property | Replacement |
|---|---|
pollTitleStyle | titleTextStyle |
pollSubtitleStyle | subtitleTextStyle |
pollOptionTextStyle, pollOptionVoteCountTextStyle | optionStyle (StreamPollOptionStyle) |
pollOptionCheckbox* properties | optionStyle.checkboxStyle (StreamCheckboxStyle) |
pollOptionVotesProgressBar* properties | optionStyle.progressBarStyle (StreamProgressBarStyle) |
pollActionButtonStyle | primaryActionStyle / secondaryActionStyle (StreamButtonThemeStyle) |
pollActionDialog* properties | Removed |
Before:
StreamPollInteractorThemeData(
pollTitleStyle: TextStyle(fontWeight: FontWeight.bold),
pollActionButtonStyle: ButtonStyle(...),
pollOptionVotesProgressBarValueColor: Colors.blue,
)After:
StreamPollInteractorThemeData(
titleTextStyle: TextStyle(fontWeight: FontWeight.bold),
primaryActionStyle: StreamButtonThemeStyle.from(borderColor: Colors.blue),
optionStyle: StreamPollOptionStyle(
progressBarStyle: StreamProgressBarStyle(fillColor: Colors.blue),
),
)StreamVoiceRecordingAttachmentThemeData
The theme has been fully redesigned:
| Removed Property | Replacement |
|---|---|
backgroundColor | Removed (handled by attachment container) |
playIcon, pauseIcon, loadingIndicator | Removed (handled by controlButtonStyle) |
audioControlButtonStyle | controlButtonStyle (StreamButtonThemeStyle) |
speedControlButtonStyle | speedToggleStyle (StreamPlaybackSpeedToggleStyle) |
audioWaveformSliderTheme | waveformStyle (StreamAudioWaveformThemeData) |
New: activeDurationTextStyle for duration display while playing.
Before:
StreamVoiceRecordingAttachmentThemeData(
backgroundColor: Colors.grey,
audioControlButtonStyle: ButtonStyle(...),
durationTextStyle: TextStyle(...),
)After:
StreamVoiceRecordingAttachmentThemeData(
controlButtonStyle: StreamButtonThemeStyle.from(...),
durationTextStyle: TextStyle(...),
activeDurationTextStyle: TextStyle(...),
)Message State & Deletion
Message deletion now supports scoped deletion modes including delete-for-me functionality.
MessageState
Introduced in: v10.0.0-beta.7
Key Changes
MessageStatefactory constructors now acceptMessageDeleteScopeinstead ofbool hardparameter- Pattern matching callbacks in state classes now receive
MessageDeleteScope scopeinstead ofbool hard - New delete-for-me functionality with dedicated states and methods
Migration Steps
Before:
final deletingState = MessageState.deleting(hard: true);
final deletedState = MessageState.deleted(hard: false);
final failedState = MessageState.deletingFailed(hard: true);
message.state.whenOrNull(
deleting: (hard) => handleDeleting(hard),
deleted: (hard) => handleDeleted(hard),
deletingFailed: (hard) => handleDeletingFailed(hard),
);After:
final deletingState = MessageState.deleting(
scope: MessageDeleteScope.hardDeleteForAll,
);
final deletedState = MessageState.deleted(
scope: MessageDeleteScope.softDeleteForAll,
);
final failedState = MessageState.deletingFailed(
scope: MessageDeleteScope.deleteForMe(),
);
message.state.whenOrNull(
deleting: (scope) => handleDeleting(scope.hard),
deleted: (scope) => handleDeleted(scope.hard),
deletingFailed: (scope) => handleDeletingFailed(scope.hard),
);
// New delete-for-me functionality
channel.deleteMessageForMe(message); // Delete only for current user
client.deleteMessageForMe(messageId); // Delete only for current user
// Check delete-for-me states
if (message.state.isDeletingForMe) { /* ... */ }
if (message.state.isDeletedForMe) { /* ... */ }
if (message.state.isDeletingForMeFailed) { /* ... */ }Important:
- All
MessageStatefactory constructors now requireMessageDeleteScopeparameter- Pattern matching callbacks receive
MessageDeleteScopeinstead ofbool hard- Use
scope.hardto access the hard delete boolean value- New delete-for-me methods are available on both
ChannelandStreamChatClient
File Upload
The file uploader interface has been expanded with standalone upload and removal methods.
AttachmentFileUploader
Introduced in: v10.0.0-beta.7
Key Changes
AttachmentFileUploaderinterface now includes four new abstract methods:uploadImage,uploadFile,removeImage, andremoveFile.- Custom implementations must implement these new standalone upload/removal methods.
Migration Steps
Before:
class CustomAttachmentFileUploader implements AttachmentFileUploader {
@override
Future<SendImageResponse> sendImage(/* ... */) async { /* ... */ }
@override
Future<SendFileResponse> sendFile(/* ... */) async { /* ... */ }
@override
Future<EmptyResponse> deleteImage(/* ... */) async { /* ... */ }
@override
Future<EmptyResponse> deleteFile(/* ... */) async { /* ... */ }
}After:
class CustomAttachmentFileUploader implements AttachmentFileUploader {
@override
Future<SendImageResponse> sendImage(/* ... */) async { /* ... */ }
@override
Future<SendFileResponse> sendFile(/* ... */) async { /* ... */ }
@override
Future<EmptyResponse> deleteImage(/* ... */) async { /* ... */ }
@override
Future<EmptyResponse> deleteFile(/* ... */) async { /* ... */ }
// New required methods
@override
Future<UploadImageResponse> uploadImage(
AttachmentFile image, {
ProgressCallback? onSendProgress,
CancelToken? cancelToken,
}) async {
// Implementation for standalone image upload
}
@override
Future<UploadFileResponse> uploadFile(
AttachmentFile file, {
ProgressCallback? onSendProgress,
CancelToken? cancelToken,
}) async {
// Implementation for standalone file upload
}
@override
Future<EmptyResponse> removeImage(
String url, {
CancelToken? cancelToken,
}) async {
// Implementation for standalone image removal
}
@override
Future<EmptyResponse> removeFile(
String url, {
CancelToken? cancelToken,
}) async {
// Implementation for standalone file removal
}
}Important:
- Custom
AttachmentFileUploaderimplementations must now implement four additional methods- The new methods support standalone uploads/removals without requiring channel context
UploadImageResponseandUploadFileResponseare aliases forSendAttachmentResponse
onAttachmentTap
Introduced in: v10.0.0-beta.9
Key Changes
onAttachmentTapcallback signature has changed to support custom attachment handling with automatic fallback to default behavior.- Callback now receives
BuildContextas the first parameter. - Returns
FutureOr<bool>to indicate whether the attachment was handled. - Returning
trueskips default behavior,falseuses default handling (URLs, images, videos, giphys).
Migration Steps
Before:
StreamMessageWidget(
message: message,
onAttachmentTap: (message, attachment) {
if (attachment.type == 'location') {
showLocationDialog(context, attachment);
}
// Other attachment types (images, videos, URLs) lost default behavior
},
)After:
StreamMessageWidget(
message: message,
onAttachmentTap: (context, message, attachment) async {
if (attachment.type == 'location') {
await showLocationDialog(context, attachment);
return true; // Handled by custom logic
}
return false; // Use default behavior for images, videos, URLs, etc.
},
)Example: Handling multiple custom types
StreamMessageWidget(
message: message,
onAttachmentTap: (context, message, attachment) async {
switch (attachment.type) {
case 'location':
await Navigator.push(
context,
MaterialPageRoute(builder: (_) => MapView(attachment)),
);
return true;
case 'product':
await showProductDialog(context, attachment);
return true;
default:
return false; // Images, videos, URLs use default viewer
}
},
)Important:
- The callback now requires
BuildContextas the first parameter- Must return
FutureOr<bool>-trueif handled,falsefor default behavior- Default behavior automatically handles URL previews, images, videos, and giphys
Appendix: Beta Release Timeline
This appendix provides a chronological reference of breaking changes by beta version for users upgrading from specific pre-release versions.
v10.0.0-beta.1
- StreamMessageWidget —
showReactionTailremoved - Reaction picker and modals required explicit
onReactionPicked(superseded by beta.13 renames)
v10.0.0-beta.3
- AttachmentPickerType
- StreamAttachmentPickerOption
- showStreamAttachmentPickerModalBottomSheet
- AttachmentPickerBottomSheet
v10.0.0-beta.4
v10.0.0-beta.7
v10.0.0-beta.8
v10.0.0-beta.9
v10.0.0-beta.12
v10.0.0-beta.13
Design refresh — all of the following are new or renamed in the final release:
- Theming —
StreamChatTheme→StreamThemeviaMaterialApp.theme.extensions - Channel List Item —
StreamChannelListTile→StreamChannelListItem, theme renames - Message Widget — props-based API, removed
show*booleans and builder callbacks - Message Actions —
StreamContextMenuAction<T>replacesStreamMessageAction,actionsBuilderreplacescustomActions - StreamMessageReactionPicker — renamed from
StreamReactionPicker - reactionIcons → reactionIconResolver
- MessageReactionsModal → ReactionDetailSheet
- Avatars — size enums,
StreamUserAvatarGroup,StreamUserAvatarStack - Message Composer —
hideSendAsDmrename,StreamChatMessageComposer - Attachment Picker result cases —
StreamAttachmentPickerSuccess/StreamAttachmentPickerCancelled - Image CDN —
StreamImageCDN.resolveUrl(),ResizeMode/CropMode - Unread Indicator — named constructors, updated callbacks
- Audio — moved to
stream_core_flutter, theming viaStreamTheme - Icons & Headers —
StreamSvgIcondeprecated, header defaults,componentBuilders - Localizations — 19 new required
Translationsmembers, changed default strings - Attachments & Polls —
StreamUrlAttachmentrenamed,shaperemoved, poll/voice theme redesign
Migration Checklist
For v10.0.0-beta.13 (Design Refresh)
- Replace
StreamChatThemewrapper withStreamThemeextension onMaterialApp.theme - Replace
StreamChannelListTilewithStreamChannelListItem; move slot customization toStreamComponentFactory - Rename
StreamChatThemeData.channelPreviewTheme→channelListItemTheme - Rename
StreamChannelPreviewThemeData→StreamChannelListItemThemeData - Rename
lastMessageAtStyle→timestampStyle - Update
messageBuildercallback signature toStreamMessageWidgetBuilder - Replace
defaultWidget.copyWith(...)withStreamMessageWidget.fromProps(props: defaultProps.copyWith(...)) - Remove all
show*boolean parameters fromStreamMessageWidget - Remove all builder callbacks (
userAvatarBuilder,textBuilder, etc.) fromStreamMessageWidget - Replace
customActions+onCustomActionTapwithactionsBuilder - Replace
StreamMessageActionwithStreamContextMenuAction - Remove
onActionTapfromStreamMessageActionsModal - Replace
showDialogwithshowStreamDialogfor Stream modals - Rename
StreamReactionPicker→StreamMessageReactionPicker - Replace
reactionIconswithreactionIconResolverinStreamChatConfigurationData - Replace
MessageReactionsModalwithReactionDetailSheet.show() - Replace avatar
constraintswithsizeenum on all avatar widgets - Rename
showOnlineStatus→showOnlineIndicator - Move avatar
onTapcallbacks to parentGestureDetectororInkWell - Rename
StreamGroupAvatar→StreamUserAvatarGroup; changemembers→users - Rename
hideSendAsDm→canAlsoSendToChannelFromThread(invert the value) - Update
UnreadIndicatorButtoncallback signatures (onTap,onDismiss→onDismissTap) - Remove
StreamUnreadIndicatorstyling params (backgroundColor,textColor,textStyle) - Replace
getResizedImageUrlString extension withStreamImageCDN.resolveUrl() - Replace string resize/crop params with
ResizeMode/CropModeenums - Remove
StreamChatThemeData.audioWaveformTheme/audioWaveformSliderTheme - Replace all
StreamSvgIcon(icon: StreamSvgIcons.*)withIcon(context.streamIcons.*)using the icon mapping table - If relying on platform-adaptive
centerTitleon headers, passcenterTitle: falseexplicitly - If relying on
elevation: 1shadow on headers, passelevation: 1explicitly - Optionally move
StreamComponentFactorywrapping intoStreamChat.componentBuilders - Replace
StreamUrlAttachmentwithStreamLinkPreviewAttachment - Replace
UrlAttachmentBuilderwithLinkPreviewAttachmentBuilder - Remove
messageThemeandhostDisplayNamefrom link preview usage - Replace
imageThumbnailSize/imageThumbnailResizeType/imageThumbnailCropTypewithImageResize? resizeonStreamImageAttachment - Remove
backgroundColorfromStreamFileAttachmentusage - Update
StreamPollInteractorThemeData— migrate to new structured properties - Update
StreamVoiceRecordingAttachmentThemeData— migrate to new design-token-based properties
For v10.0.0-beta.12
- Replace
ArgumentError('The size of the attachment is...')catches withAttachmentTooLargeError - Replace
ArgumentError('The maximum number of attachments is...')catches withAttachmentLimitReachedError
For v10.0.0-beta.9
- Update
onAttachmentTapcallback signature to includeBuildContextas first parameter - Return
FutureOr<bool>fromonAttachmentTap—trueif handled,falsefor default behavior - Update any direct usage of
ReactionPickerIconListto handle reaction selection state externally
For v10.0.0-beta.8
- Replace
customAttachmentPickerOptionswithattachmentPickerOptionsBuilder - Replace
onCustomAttachmentPickerResultwithonAttachmentPickerResult(returnsFutureOr<bool>)
For v10.0.0-beta.7
- Update custom
AttachmentFileUploaderimplementations with four new abstract methods:uploadImage,uploadFile,removeImage,removeFile - Update
MessageStatefactory constructors to useMessageDeleteScopeparameter - Update pattern-matching callbacks to handle
MessageDeleteScopeinstead ofbool hard - Adopt new delete-for-me functionality with
deleteMessageForMemethods where needed
For v10.0.0-beta.4
- Update
sendReactionmethod calls to useReactionobject instead of individual parameters
For v10.0.0-beta.3
- Update attachment picker options to use
SystemAttachmentPickerOptionorTabbedAttachmentPickerOption - Handle
StreamAttachmentPickerResultreturn type from attachment picker (AttachmentsPicked/PollCreated/AttachmentPickerError/CustomAttachmentPickerResult) - Use renamed bottom sheet classes (
StreamTabbedAttachmentPickerBottomSheet,StreamSystemAttachmentPickerBottomSheet)
For additional support, visit our GitHub repository.
- Table of Contents
- Who Should Read This
- Quick Reference
- Theming
- Channel List Item
- Message Widget
- Message Actions
- Reactions
- Avatars
- Message Composer
- Attachment Picker
- Image CDN
- Unread Indicator
- Audio
- Icons & Headers
- Attachments & Polls
- Message State & Deletion
- File Upload
- onAttachmentTap
- Appendix: Beta Release Timeline
- Migration Checklist