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

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

Upgrading FromSections to Review
v9.xAll sections
v10.0.0-beta.1All sections introduced after beta.1
v10.0.0-beta.3Sections introduced in beta.4 and later
v10.0.0-beta.4Sections introduced in beta.7 and later
v10.0.0-beta.7Sections introduced in beta.8 and later
v10.0.0-beta.8Sections introduced in beta.9 and later
v10.0.0-beta.9Sections introduced in beta.12 and later
v10.0.0-beta.12Sections introduced in beta.13 (design refresh)
v10.0.0-beta.13No 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 AreaKey Changes
ThemingStreamChatTheme wrapper replaced by StreamTheme extension on MaterialApp.theme
Channel ListStreamChannelListTileStreamChannelListItem, theme class renames
Message WidgetProps-based API via StreamMessageWidgetProps, removed show* booleans and builder callbacks
Message ActionsStreamContextMenuAction<T> replaces StreamMessageAction, actionsBuilder replaces customActions
ReactionsReaction object API, StreamMessageReactionPicker, reactionIconResolver, ReactionDetailSheet
AvatarsSize enums replace BoxConstraints, StreamGroupAvatarStreamUserAvatarGroup
Message ComposerhideSendAsDmcanAlsoSendToChannelFromThread (inverted), new StreamChatMessageComposer
Attachment PickerSealed class hierarchy (AttachmentsPicked, PollCreated, AttachmentPickerError), builder pattern for options, typed errors
Image CDNgetResizedImageUrlStreamImageCDN.resolveUrl(), ResizeMode/CropMode enums
Unread IndicatorNamed constructors, updated UnreadIndicatorButton callbacks
AudioMoved to stream_core_flutter, theming via StreamTheme
Icons & HeadersStreamSvgIcon deprecated → Icon(context.streamIcons.*), header default changes
Message StateMessageDeleteScope replaces bool hard, delete-for-me support
File UploadFour new abstract methods on AttachmentFileUploader
onAttachmentTapNew 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:

StreamChatTheme(
  data: StreamChatThemeData(
    colorTheme: StreamColorTheme.light(
      accentPrimary: Colors.blue,
    ),
  ),
  child: MaterialApp(...),
)

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

OldNew
StreamChatThemeData.channelPreviewThemeStreamChatThemeData.channelListItemTheme
StreamChannelPreviewThemeDataStreamChannelListItemThemeData
StreamChannelPreviewTheme.of(context)StreamChannelListItemTheme.of(context)
lastMessageAtStyletimestampStyle
lastMessageAtFormatter (theme prop)ChannelLastMessageDate(formatter: ...) widget param
tileColor / selectedTileColorbackgroundColor: 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 ParameterMigration Path
showReactionsStreamMessageItemThemeData visibility
showDeleteMessage / showEditMessageChannel permissions
showUsername / showTimestamp / showSendingIndicatorStreamMessageItemThemeData.footerVisibility
showUserAvatarStreamMessageItemThemeData.leadingVisibility
showThreadReplyIndicatorShown automatically when replyCount > 0
showReactionTailTail is shown automatically when the picker is visible

Removed Builder Callbacks

Old ParameterMigration Path
userAvatarBuilderComponent factory (replace DefaultStreamMessage)
textBuilderComponent factory (replace StreamMessageContent)
quotedMessageBuilderComponent factory (replace StreamMessageContent)
deletedMessageBuilderComponent factory (replace StreamMessageContent)
reactionPickerBuilderStreamChatConfigurationData.reactionIconResolver
customActionsactionsBuilder on StreamMessageWidgetProps
onCustomActionTaponTap per StreamContextMenuAction

Changed Callback Signatures

OldNew
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

OldNew
MessageBuilder = Widget Function(BuildContext, MessageDetails, List<Message>, StreamMessageWidget)StreamMessageWidgetBuilder = Widget Function(BuildContext, Message, StreamMessageWidgetProps)
ParentMessageBuilderStreamMessageWidgetBuilder
StreamDeletedMessageStreamMessageDeleted

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:

StreamMessageActionStreamContextMenuAction
action: Tvalue: T?
title: Widget?label: Widget (now required)
leading: Widget?leading: Widget?
isDestructive: boolisDestructive: 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

  • sendReaction method now accepts a full Reaction object 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 sendReaction method now requires a Reaction object
  • Optional parameters like enforceUnique and skipPush remain as method parameters
  • You can now specify custom emoji codes for reactions using the emojiCode field

StreamMessageReactionPicker

Introduced in: v10.0.0-beta.13 (renamed from StreamReactionPicker)

Key Changes

  • StreamReactionPicker has been renamed to StreamMessageReactionPicker
  • onReactionPicked is 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 an onReactionPicked callback.


ReactionPickerIconList

Introduced in: v10.0.0-beta.9

Key Changes

  • message parameter has been removed
  • reactionIcons type changed from List<StreamReactionIcon> to List<ReactionPickerIcon>
  • onReactionPicked callback renamed to onIconPicked with new signature: ValueSetter<ReactionPickerIcon>
  • iconBuilder parameter 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 StreamMessageReactionPicker for most use cases instead of ReactionPickerIconList

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:

  • showOnlineStatus is renamed to showOnlineIndicator
  • onTap has been removed from avatar widgets — wrap with GestureDetector or InkWell instead

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)

OldNew
hideSendAsDm: truecanAlsoSendToChannelFromThread: false
hideSendAsDm: falsecanAlsoSendToChannelFromThread: 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

  • AttachmentPickerType enum replaced with sealed class hierarchy
  • Now supports extensible custom types like contact and location pickers
  • Use built-in types like AttachmentPickerType.images or define your own via CustomAttachmentPickerType

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

  • StreamAttachmentPickerOption replaced with two sealed classes:
    • SystemAttachmentPickerOption for system pickers (camera, files)
    • TabbedAttachmentPickerOption for 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 SystemAttachmentPickerOption for system pickers (camera, file dialogs)
  • Use TabbedAttachmentPickerOption for custom UI pickers (gallery, polls)

showStreamAttachmentPickerModalBottomSheet

Introduced in: v10.0.0-beta.3

Key Changes

  • Now returns StreamAttachmentPickerResult instead of AttachmentPickerValue
  • Result cases updated in beta.13: StreamAttachmentPickerSuccess and StreamAttachmentPickerCancelled

Migration Steps

Before:

final result = await showStreamAttachmentPickerModalBottomSheet(
  context: context,
  controller: controller,
);

// result is AttachmentPickerValue

After:

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 StreamAttachmentPickerResult return type with proper switch cases.


AttachmentPickerBottomSheet

Introduced in: v10.0.0-beta.3

Key Changes

  • StreamMobileAttachmentPickerBottomSheetStreamTabbedAttachmentPickerBottomSheet
  • StreamWebOrDesktopAttachmentPickerBottomSheetStreamSystemAttachmentPickerBottomSheet

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

  • customAttachmentPickerOptions has been removed. Use attachmentPickerOptionsBuilder instead.
  • 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

  • onCustomAttachmentPickerResult has been removed. Use onAttachmentPickerResult which returns FutureOr<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:

  • onAttachmentPickerResult replaces onCustomAttachmentPickerResult and must return a boolean
  • Return true from onAttachmentPickerResult to skip default handling
  • Return false to 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...') with AttachmentTooLargeError.
  • Replaced ArgumentError('The maximum number of attachments is...') with AttachmentLimitReachedError.

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 ArgumentError catches with the specific typed errors
  • AttachmentTooLargeError provides fileSize and maxSize properties
  • AttachmentLimitReachedError provides maxCount property

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 CachedNetworkImage

For 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 threads

UnreadIndicatorButton

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:

ParameterOld defaultNew default
centerTitlenull (platform-adaptive)true
elevation10
scrolledUnderElevation0 (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:

FieldTypeDefaultDescription
attachmentBuildersList<StreamAttachmentWidgetBuilder>?nullCustom attachment widget builders, prepended to built-in builders
reactionTypeStreamReactionsType?nullControls visual style of the reactions display
reactionPositionStreamReactionsPosition?nullControls where reactions appear relative to the message bubble

Note: The imageCDN field 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:

  • shape parameter removed
  • constraints changed from required to optional

Per-widget changes:

WidgetAdditional Changes
StreamImageAttachmentimageThumbnailSize/imageThumbnailResizeType/imageThumbnailCropType → single ImageResize? resize
StreamFileAttachmentbackgroundColor removed
StreamUrlAttachmentRenamed 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 PropertyReplacement
pollTitleStyletitleTextStyle
pollSubtitleStylesubtitleTextStyle
pollOptionTextStyle, pollOptionVoteCountTextStyleoptionStyle (StreamPollOptionStyle)
pollOptionCheckbox* propertiesoptionStyle.checkboxStyle (StreamCheckboxStyle)
pollOptionVotesProgressBar* propertiesoptionStyle.progressBarStyle (StreamProgressBarStyle)
pollActionButtonStyleprimaryActionStyle / secondaryActionStyle (StreamButtonThemeStyle)
pollActionDialog* propertiesRemoved

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 PropertyReplacement
backgroundColorRemoved (handled by attachment container)
playIcon, pauseIcon, loadingIndicatorRemoved (handled by controlButtonStyle)
audioControlButtonStylecontrolButtonStyle (StreamButtonThemeStyle)
speedControlButtonStylespeedToggleStyle (StreamPlaybackSpeedToggleStyle)
audioWaveformSliderThemewaveformStyle (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

  • MessageState factory constructors now accept MessageDeleteScope instead of bool hard parameter
  • Pattern matching callbacks in state classes now receive MessageDeleteScope scope instead of bool 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 MessageState factory constructors now require MessageDeleteScope parameter
  • Pattern matching callbacks receive MessageDeleteScope instead of bool hard
  • Use scope.hard to access the hard delete boolean value
  • New delete-for-me methods are available on both Channel and StreamChatClient

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

  • AttachmentFileUploader interface now includes four new abstract methods: uploadImage, uploadFile, removeImage, and removeFile.
  • 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 AttachmentFileUploader implementations must now implement four additional methods
  • The new methods support standalone uploads/removals without requiring channel context
  • UploadImageResponse and UploadFileResponse are aliases for SendAttachmentResponse

onAttachmentTap

Introduced in: v10.0.0-beta.9

Key Changes

  • onAttachmentTap callback signature has changed to support custom attachment handling with automatic fallback to default behavior.
  • Callback now receives BuildContext as the first parameter.
  • Returns FutureOr<bool> to indicate whether the attachment was handled.
  • Returning true skips default behavior, false uses 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 BuildContext as the first parameter
  • Must return FutureOr<bool> - true if handled, false for 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

  • StreamMessageWidgetshowReactionTail removed
  • Reaction picker and modals required explicit onReactionPicked (superseded by beta.13 renames)

v10.0.0-beta.3

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:


Migration Checklist

For v10.0.0-beta.13 (Design Refresh)

  • Replace StreamChatTheme wrapper with StreamTheme extension on MaterialApp.theme
  • Replace StreamChannelListTile with StreamChannelListItem; move slot customization to StreamComponentFactory
  • Rename StreamChatThemeData.channelPreviewThemechannelListItemTheme
  • Rename StreamChannelPreviewThemeDataStreamChannelListItemThemeData
  • Rename lastMessageAtStyletimestampStyle
  • Update messageBuilder callback signature to StreamMessageWidgetBuilder
  • Replace defaultWidget.copyWith(...) with StreamMessageWidget.fromProps(props: defaultProps.copyWith(...))
  • Remove all show* boolean parameters from StreamMessageWidget
  • Remove all builder callbacks (userAvatarBuilder, textBuilder, etc.) from StreamMessageWidget
  • Replace customActions + onCustomActionTap with actionsBuilder
  • Replace StreamMessageAction with StreamContextMenuAction
  • Remove onActionTap from StreamMessageActionsModal
  • Replace showDialog with showStreamDialog for Stream modals
  • Rename StreamReactionPickerStreamMessageReactionPicker
  • Replace reactionIcons with reactionIconResolver in StreamChatConfigurationData
  • Replace MessageReactionsModal with ReactionDetailSheet.show()
  • Replace avatar constraints with size enum on all avatar widgets
  • Rename showOnlineStatusshowOnlineIndicator
  • Move avatar onTap callbacks to parent GestureDetector or InkWell
  • Rename StreamGroupAvatarStreamUserAvatarGroup; change membersusers
  • Rename hideSendAsDmcanAlsoSendToChannelFromThread (invert the value)
  • Update UnreadIndicatorButton callback signatures (onTap, onDismissonDismissTap)
  • Remove StreamUnreadIndicator styling params (backgroundColor, textColor, textStyle)
  • Replace getResizedImageUrl String extension with StreamImageCDN.resolveUrl()
  • Replace string resize/crop params with ResizeMode / CropMode enums
  • Remove StreamChatThemeData.audioWaveformTheme / audioWaveformSliderTheme
  • Replace all StreamSvgIcon(icon: StreamSvgIcons.*) with Icon(context.streamIcons.*) using the icon mapping table
  • If relying on platform-adaptive centerTitle on headers, pass centerTitle: false explicitly
  • If relying on elevation: 1 shadow on headers, pass elevation: 1 explicitly
  • Optionally move StreamComponentFactory wrapping into StreamChat.componentBuilders
  • Replace StreamUrlAttachment with StreamLinkPreviewAttachment
  • Replace UrlAttachmentBuilder with LinkPreviewAttachmentBuilder
  • Remove messageTheme and hostDisplayName from link preview usage
  • Replace imageThumbnailSize/imageThumbnailResizeType/imageThumbnailCropType with ImageResize? resize on StreamImageAttachment
  • Remove backgroundColor from StreamFileAttachment usage
  • 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 with AttachmentTooLargeError
  • Replace ArgumentError('The maximum number of attachments is...') catches with AttachmentLimitReachedError

For v10.0.0-beta.9

  • Update onAttachmentTap callback signature to include BuildContext as first parameter
  • Return FutureOr<bool> from onAttachmentTaptrue if handled, false for default behavior
  • Update any direct usage of ReactionPickerIconList to handle reaction selection state externally

For v10.0.0-beta.8

  • Replace customAttachmentPickerOptions with attachmentPickerOptionsBuilder
  • Replace onCustomAttachmentPickerResult with onAttachmentPickerResult (returns FutureOr<bool>)

For v10.0.0-beta.7

  • Update custom AttachmentFileUploader implementations with four new abstract methods: uploadImage, uploadFile, removeImage, removeFile
  • Update MessageState factory constructors to use MessageDeleteScope parameter
  • Update pattern-matching callbacks to handle MessageDeleteScope instead of bool hard
  • Adopt new delete-for-me functionality with deleteMessageForMe methods where needed

For v10.0.0-beta.4

  • Update sendReaction method calls to use Reaction object instead of individual parameters

For v10.0.0-beta.3

  • Update attachment picker options to use SystemAttachmentPickerOption or TabbedAttachmentPickerOption
  • Handle StreamAttachmentPickerResult return type from attachment picker (AttachmentsPicked / PollCreated / AttachmentPickerError / CustomAttachmentPickerResult)
  • Use renamed bottom sheet classes (StreamTabbedAttachmentPickerBottomSheet, StreamSystemAttachmentPickerBottomSheet)

For additional support, visit our GitHub repository.