# 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](#who-should-read-this)
- [Quick Reference](#quick-reference)
- [Theming](#theming)
- [Channel List Item](#channel-list-item)
- [Message Widget](#message-widget)
- [Message Actions](#message-actions)
- [Reactions](#reactions)
  - [SendReaction](#sendreaction)
  - [StreamMessageReactionPicker](#streammessagereactionpicker)
  - [reactionIcons → reactionIconResolver](#reactionicons--reactioniconresolver)
  - [MessageReactionsModal → ReactionDetailSheet](#messagereactionsmodal--reactiondetailsheet)
  - [ReactionPickerIconList](#reactionpickericonlist)
- [Avatars](#avatars)
- [Message Composer](#message-composer)
- [Attachment Picker](#attachment-picker)
  - [AttachmentPickerType](#attachmentpickertype)
  - [StreamAttachmentPickerOption](#streamattachmentpickeroption)
  - [showStreamAttachmentPickerModalBottomSheet](#showstreamattachmentpickermodalbottomsheet)
  - [AttachmentPickerBottomSheet](#attachmentpickerbottomsheet)
  - [customAttachmentPickerOptions](#customattachmentpickeroptions)
  - [onCustomAttachmentPickerResult](#oncustomattachmentpickerresult)
  - [StreamAttachmentPickerController](#streamattachmentpickercontroller)
- [Image CDN](#image-cdn)
- [Unread Indicator](#unread-indicator)
- [Audio](#audio)
- [Icons & Headers](#icons--headers)
- [Localizations](#localizations)
- [Attachments & Polls](#attachments--polls)
- [Message State & Deletion](#message-state--deletion)
- [File Upload](#file-upload)
- [onAttachmentTap](#onattachmenttap)
- [Appendix: Beta Release Timeline](#appendix-beta-release-timeline)
- [Migration Checklist](#migration-checklist)

---

## Who Should Read This

| Upgrading From                       | Sections to Review                              |
| ------------------------------------ | ----------------------------------------------- |
| **v9.x**                             | All sections                                    |
| [**v10.0.0-beta.1**](#v1000-beta1)   | All sections introduced after beta.1            |
| [**v10.0.0-beta.3**](#v1000-beta3)   | Sections introduced in beta.4 and later         |
| [**v10.0.0-beta.4**](#v1000-beta4)   | Sections introduced in beta.7 and later         |
| [**v10.0.0-beta.7**](#v1000-beta7)   | Sections introduced in beta.8 and later         |
| [**v10.0.0-beta.8**](#v1000-beta8)   | Sections introduced in beta.9 and later         |
| [**v10.0.0-beta.9**](#v1000-beta9)   | Sections introduced in beta.12 and later        |
| [**v10.0.0-beta.12**](#v1000-beta12) | Sections introduced in beta.13 (design refresh) |
| [**v10.0.0-beta.13**](#v1000-beta13) | 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**](#theming)                       | `StreamChatTheme` wrapper replaced by `StreamTheme` extension on `MaterialApp.theme`                                            |
| [**Channel List**](#channel-list-item)        | `StreamChannelListTile` → `StreamChannelListItem`, theme class renames                                                          |
| [**Message Widget**](#message-widget)         | Props-based API via `StreamMessageWidgetProps`, removed `show*` booleans and builder callbacks                                  |
| [**Message Actions**](#message-actions)       | `StreamContextMenuAction<T>` replaces `StreamMessageAction`, `actionsBuilder` replaces `customActions`                          |
| [**Reactions**](#reactions)                   | `Reaction` object API, `StreamMessageReactionPicker`, `reactionIconResolver`, `ReactionDetailSheet`                             |
| [**Avatars**](#avatars)                       | Size enums replace `BoxConstraints`, `StreamGroupAvatar` → `StreamUserAvatarGroup`                                              |
| [**Message Composer**](#message-composer)     | `hideSendAsDm` → `canAlsoSendToChannelFromThread` (inverted), new `StreamChatMessageComposer`                                   |
| [**Attachment Picker**](#attachment-picker)   | Sealed class hierarchy (`AttachmentsPicked`, `PollCreated`, `AttachmentPickerError`), builder pattern for options, typed errors |
| [**Image CDN**](#image-cdn)                   | `getResizedImageUrl` → `StreamImageCDN.resolveUrl()`, `ResizeMode`/`CropMode` enums                                             |
| [**Unread Indicator**](#unread-indicator)     | Named constructors, updated `UnreadIndicatorButton` callbacks                                                                   |
| [**Audio**](#audio)                           | Moved to `stream_core_flutter`, theming via `StreamTheme`                                                                       |
| [**Icons & Headers**](#icons--headers)        | `StreamSvgIcon` deprecated → `Icon(context.streamIcons.*)`, header default changes                                              |
| [**Message State**](#message-state--deletion) | `MessageDeleteScope` replaces `bool hard`, delete-for-me support                                                                |
| [**File Upload**](#file-upload)               | Four new abstract methods on `AttachmentFileUploader`                                                                           |
| [**onAttachmentTap**](#onattachmenttap)       | New `FutureOr<bool>` signature with `BuildContext` and fallback support                                                         |

---

## Theming

> **Introduced in:** [v10.0.0-beta.13](https://pub.dev/packages/stream_chat_flutter/versions/10.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:**

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

**After:**

```dart
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:

```dart
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](https://pub.dev/packages/stream_chat_flutter/versions/10.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:**

```dart
StreamChannelListTile(
  channel: channel,
  onTap: () => openChannel(channel),
  tileColor: Colors.white,
  selectedTileColor: Colors.blue.shade50,
  selected: isSelected,
  leading: StreamChannelAvatar(channel: channel),
  title: StreamChannelName(channel: channel),
)
```

**After:**

```dart
StreamChannelListItem(
  channel: channel,
  onTap: () => openChannel(channel),
  selected: isSelected,
)
```

To customize slots, use `StreamComponentFactory`:

```dart
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:**

```dart
StreamChatThemeData(
  channelPreviewTheme: StreamChannelPreviewThemeData(
    titleStyle: TextStyle(fontWeight: FontWeight.bold),
    lastMessageAtStyle: TextStyle(fontSize: 12),
    unreadCounterColor: Colors.red,
  ),
)
```

**After:**

```dart
StreamChatThemeData(
  channelListItemTheme: StreamChannelListItemThemeData(
    titleStyle: const TextStyle(fontWeight: FontWeight.bold),
    timestampStyle: const TextStyle(fontSize: 12),
    // unreadCounterColor → use StreamBadgeNotificationThemeData
  ),
)
```

**State-dependent colors:**

```dart
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](https://pub.dev/packages/stream_chat_flutter/versions/10.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):**

```dart
StreamMessageListView(
  messageBuilder: (context, details, messages, defaultWidget) {
    return defaultWidget.copyWith(showReactions: false);
  },
)
```

**After (messageBuilder with copyWith on props):**

```dart
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:

```dart
// Before
StreamMessageWidget(message: message, messageTheme: streamTheme.ownMessageTheme)

// After
StreamMessageWidget(message: message)
```

---

## Message Actions

> **Introduced in:** [v10.0.0-beta.13](https://pub.dev/packages/stream_chat_flutter/versions/10.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:**

```dart
StreamMessageAction(
  action: CopyMessage(message: message),
  leading: const StreamSvgIcon(icon: StreamSvgIcons.copy),
  title: Text('Copy'),
)
```

**After:**

```dart
StreamContextMenuAction<MessageAction>(
  value: CopyMessage(message: message),
  leading: Icon(context.streamIcons.copy),
  label: const Text('Copy'),
  onTap: () => _copyMessage(message),
)
```

### customActions + onCustomActionTap → actionsBuilder

**Before:**

```dart
StreamMessageWidget(
  message: message,
  customActions: [
    StreamMessageAction(
      action: CustomMessageAction(message: message),
      leading: const Icon(Icons.star),
      title: const Text('Favourite'),
    ),
  ],
  onCustomActionTap: (action) => _favourite(action.message),
)
```

**After:**

```dart
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:

```dart
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](https://pub.dev/packages/stream_chat_flutter/versions/10.0.0-beta.4)

#### Key Changes

- `sendReaction` method now accepts a full `Reaction` object instead of individual parameters.

#### Migration Steps

**Before:**

```dart
channel.sendReaction(
  message,
  'like',
  score: 1,
  extraData: {'custom_field': 'value'},
);

client.sendReaction(
  messageId,
  'love',
  enforceUnique: true,
  extraData: {'custom_field': 'value'},
);
```

**After:**

```dart
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](https://pub.dev/packages/stream_chat_flutter/versions/10.0.0-beta.13) (renamed from `StreamReactionPicker`)

#### Key Changes

- `StreamReactionPicker` has been renamed to `StreamMessageReactionPicker`
- `onReactionPicked` is required

#### Migration Steps

**Before:**

```dart
StreamReactionPicker(message: message)
```

**After:**

```dart
StreamMessageReactionPicker(
  message: message,
  onReactionPicked: onReactionPicked,
)
```

---

### reactionIcons → reactionIconResolver

> **Introduced in:** [v10.0.0-beta.13](https://pub.dev/packages/stream_chat_flutter/versions/10.0.0-beta.13)

#### Migration Steps

**Before:**

```dart
StreamChatConfigurationData(
  reactionIcons: [/* list of icon builders */],
)
```

**After:**

```dart
StreamChatConfigurationData(
  reactionIconResolver: const MyReactionIconResolver(),
)
```

Extend `DefaultReactionIconResolver` to customize the available reactions:

```dart
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](https://pub.dev/packages/stream_chat_flutter/versions/10.0.0-beta.13)

#### Migration Steps

**Before:**

```dart
showDialog(
  context: context,
  builder: (_) => MessageReactionsModal(message: message),
)
```

**After:**

```dart
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](https://pub.dev/packages/stream_chat_flutter/versions/10.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:**

```dart
ReactionPickerIconList(
  message: message,
  reactionIcons: icons,
  onReactionPicked: (reaction) {
    channel.sendReaction(message, reaction);
  },
)
```

**After:**

```dart
// 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](https://pub.dev/packages/stream_chat_flutter/versions/10.0.0-beta.13)

### constraints → size enum

All avatar components now use size enums instead of `BoxConstraints`.

**Before:**

```dart
StreamUserAvatar(
  user: user,
  constraints: BoxConstraints.tight(const Size(40, 40)),
  showOnlineStatus: true,
  onTap: (user) => showProfile(user),
)
```

**After:**

```dart
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

```dart
// 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):

```dart
StreamUserAvatarStack(
  users: threadParticipants,
  size: StreamAvatarStackSize.xs,
  max: 3,
)
```

---

## Message Composer

> **Introduced in:** [v10.0.0-beta.13](https://pub.dev/packages/stream_chat_flutter/versions/10.0.0-beta.13)

### hideSendAsDm → canAlsoSendToChannelFromThread (logic inverted)

| Old                   | New                                                  |
| --------------------- | ---------------------------------------------------- |
| `hideSendAsDm: true`  | `canAlsoSendToChannelFromThread: false`              |
| `hideSendAsDm: false` | `canAlsoSendToChannelFromThread: true` (new default) |

```dart
// 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](https://pub.dev/packages/stream_chat_flutter/versions/10.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:**

```dart
// Using enum-based attachment types
final attachmentType = AttachmentPickerType.images;
```

**After:**

```dart
// 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](https://pub.dev/packages/stream_chat_flutter/versions/10.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:**

```dart
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:**

```dart
// 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](https://pub.dev/packages/stream_chat_flutter/versions/10.0.0-beta.3)

#### Key Changes

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

#### Migration Steps

**Before:**

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

// result is AttachmentPickerValue
```

**After:**

```dart
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](https://pub.dev/packages/stream_chat_flutter/versions/10.0.0-beta.3)

#### Key Changes

- `StreamMobileAttachmentPickerBottomSheet` → `StreamTabbedAttachmentPickerBottomSheet`
- `StreamWebOrDesktopAttachmentPickerBottomSheet` → `StreamSystemAttachmentPickerBottomSheet`

#### Migration Steps

**Before:**

```dart
StreamMobileAttachmentPickerBottomSheet(
  context: context,
  controller: controller,
  customOptions: [option],
);

StreamWebOrDesktopAttachmentPickerBottomSheet(
  context: context,
  controller: controller,
  customOptions: [option],
);
```

**After:**

```dart
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](https://pub.dev/packages/stream_chat_flutter/versions/10.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:**

```dart
StreamMessageInput(
  customAttachmentPickerOptions: [
    TabbedAttachmentPickerOption(
      key: 'custom-location',
      icon: const Icon(Icons.location_on),
      supportedTypes: [AttachmentPickerType.images],
      optionViewBuilder: (context, controller) {
        return CustomLocationPicker();
      },
    ),
  ],
)
```

**After:**

```dart
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**

```dart
StreamMessageInput(
  attachmentPickerOptionsBuilder: (context, defaultOptions) {
    // Remove poll option
    return defaultOptions.where((option) => option.key != 'poll').toList();
  },
)
```

**Example: Reordering options**

```dart
StreamMessageInput(
  attachmentPickerOptionsBuilder: (context, defaultOptions) {
    return defaultOptions.reversed.toList();
  },
)
```

**Using with `showStreamAttachmentPickerModalBottomSheet`:**

```dart
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](https://pub.dev/packages/stream_chat_flutter/versions/10.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:**

```dart
StreamMessageInput(
  onCustomAttachmentPickerResult: (result) {
    if (result is CustomAttachmentPickerResult) {
      final data = result.data;
      // Handle custom result
    }
  },
)
```

**After:**

```dart
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](https://pub.dev/packages/stream_chat_flutter/versions/10.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:**

```dart
try {
  await controller.addAttachment(attachment);
} on ArgumentError catch (e) {
  showError(e.message);
}
```

**After:**

```dart
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](https://pub.dev/packages/stream_chat_flutter/versions/10.0.0-beta.13)

### getResizedImageUrl → StreamImageCDN.resolveUrl

**Before:**

```dart
final url = imageUrl.getResizedImageUrl(
  width: 200,
  height: 300,
  resize: 'clip',
  crop: 'center',
);
```

**After:**

```dart
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:

```dart
// 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](https://pub.dev/packages/stream_chat_flutter/versions/10.0.0-beta.13)

### StreamUnreadIndicator

Styling params (`backgroundColor`, `textColor`, `textStyle`) removed — styling is now controlled via `StreamTheme`.

Named constructors added:

```dart
StreamUnreadIndicator()             // total unread messages
StreamUnreadIndicator.channels()    // unread channels
StreamUnreadIndicator.threads()     // unread threads
```

### UnreadIndicatorButton

Callback signatures changed:

```dart
// Before
UnreadIndicatorButton(
  onTap: () => _scrollToUnread(),
  onDismiss: () => _markAllRead(),
)

// After
UnreadIndicatorButton(
  onTap: (lastReadMessageId) async => _scrollToUnread(lastReadMessageId),
  onDismissTap: () async => _markAllRead(),
)
```

---

## Audio

> **Introduced in:** [v10.0.0-beta.13](https://pub.dev/packages/stream_chat_flutter/versions/10.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`).

```dart
// 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](https://pub.dev/packages/stream_chat_flutter/versions/10.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:**

```dart
StreamSvgIcon(icon: StreamSvgIcons.reply)
StreamSvgIcon(icon: StreamSvgIcons.copy, color: Colors.red, size: 24)
```

**After:**

```dart
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)   |

```dart
// 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:**

```dart
StreamComponentFactory(
  builders: StreamComponentBuilders(
    extensions: streamChatComponentBuilders(
      messageWidget: (context, props) => MyMessage(props: props),
    ),
  ),
  child: StreamChat(client: client, child: MyApp()),
)
```

**After:**

```dart
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 `imageCDN` field was also added — see the [Image CDN](#image-cdn) section.

---

## Attachments & Polls

> **Introduced in:** [v10.0.0-beta.13](https://pub.dev/packages/stream_chat_flutter/versions/10.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:**

| Widget                  | Additional Changes                                                                                      |
| ----------------------- | ------------------------------------------------------------------------------------------------------- |
| `StreamImageAttachment` | `imageThumbnailSize`/`imageThumbnailResizeType`/`imageThumbnailCropType` → single `ImageResize? resize` |
| `StreamFileAttachment`  | `backgroundColor` removed                                                                               |
| `StreamUrlAttachment`   | **Renamed** to `StreamLinkPreviewAttachment`; `messageTheme` and `hostDisplayName` removed              |

### StreamUrlAttachment → StreamLinkPreviewAttachment

**Before:**

```dart
StreamUrlAttachment(
  message: message,
  urlAttachment: attachment,
  messageTheme: theme.ownMessageTheme,
  hostDisplayName: 'GitHub',
)
```

**After:**

```dart
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:**

```dart
StreamPollInteractorThemeData(
  pollTitleStyle: TextStyle(fontWeight: FontWeight.bold),
  pollActionButtonStyle: ButtonStyle(...),
  pollOptionVotesProgressBarValueColor: Colors.blue,
)
```

**After:**

```dart
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:**

```dart
StreamVoiceRecordingAttachmentThemeData(
  backgroundColor: Colors.grey,
  audioControlButtonStyle: ButtonStyle(...),
  durationTextStyle: TextStyle(...),
)
```

**After:**

```dart
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](https://pub.dev/packages/stream_chat_flutter/versions/10.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:**

```dart
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:**

```dart
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](https://pub.dev/packages/stream_chat_flutter/versions/10.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:**

```dart
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:**

```dart
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](https://pub.dev/packages/stream_chat_flutter/versions/10.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:**

```dart
StreamMessageWidget(
  message: message,
  onAttachmentTap: (message, attachment) {
    if (attachment.type == 'location') {
      showLocationDialog(context, attachment);
    }
    // Other attachment types (images, videos, URLs) lost default behavior
  },
)
```

**After:**

```dart
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**

```dart
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

- [StreamMessageWidget](#message-widget) — `showReactionTail` removed
- Reaction picker and modals required explicit `onReactionPicked` (superseded by beta.13 renames)

### v10.0.0-beta.3

- [AttachmentPickerType](#attachmentpickertype)
- [StreamAttachmentPickerOption](#streamattachmentpickeroption)
- [showStreamAttachmentPickerModalBottomSheet](#showstreamattachmentpickermodalbottomsheet)
- [AttachmentPickerBottomSheet](#attachmentpickerbottomsheet)

### v10.0.0-beta.4

- [SendReaction](#sendreaction)

### v10.0.0-beta.7

- [AttachmentFileUploader](#attachmentfileuploader)
- [MessageState](#messagestate)

### v10.0.0-beta.8

- [customAttachmentPickerOptions](#customattachmentpickeroptions)
- [onCustomAttachmentPickerResult](#oncustomattachmentpickerresult)

### v10.0.0-beta.9

- [onAttachmentTap](#onattachmenttap)
- [ReactionPickerIconList](#reactionpickericonlist)

### v10.0.0-beta.12

- [StreamAttachmentPickerController](#streamattachmentpickercontroller)

### v10.0.0-beta.13

Design refresh — all of the following are new or renamed in the final release:

- [Theming](#theming) — `StreamChatTheme` → `StreamTheme` via `MaterialApp.theme.extensions`
- [Channel List Item](#channel-list-item) — `StreamChannelListTile` → `StreamChannelListItem`, theme renames
- [Message Widget](#message-widget) — props-based API, removed `show*` booleans and builder callbacks
- [Message Actions](#message-actions) — `StreamContextMenuAction<T>` replaces `StreamMessageAction`, `actionsBuilder` replaces `customActions`
- [StreamMessageReactionPicker](#streammessagereactionpicker) — renamed from `StreamReactionPicker`
- [reactionIcons → reactionIconResolver](#reactionicons--reactioniconresolver)
- [MessageReactionsModal → ReactionDetailSheet](#messagereactionsmodal--reactiondetailsheet)
- [Avatars](#avatars) — size enums, `StreamUserAvatarGroup`, `StreamUserAvatarStack`
- [Message Composer](#message-composer) — `hideSendAsDm` rename, `StreamChatMessageComposer`
- [Attachment Picker result cases](#showstreamattachmentpickermodalbottomsheet) — `StreamAttachmentPickerSuccess` / `StreamAttachmentPickerCancelled`
- [Image CDN](#image-cdn) — `StreamImageCDN.resolveUrl()`, `ResizeMode`/`CropMode`
- [Unread Indicator](#unread-indicator) — named constructors, updated callbacks
- [Audio](#audio) — moved to `stream_core_flutter`, theming via `StreamTheme`
- [Icons & Headers](#icons--headers) — `StreamSvgIcon` deprecated, header defaults, `componentBuilders`
- [Localizations](#localizations) — 19 new required `Translations` members, changed default strings
- [Attachments & Polls](#attachments--polls) — `StreamUrlAttachment` renamed, `shape` removed, poll/voice theme redesign

---

## 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.channelPreviewTheme` → `channelListItemTheme`
- [ ] Rename `StreamChannelPreviewThemeData` → `StreamChannelListItemThemeData`
- [ ] Rename `lastMessageAtStyle` → `timestampStyle`
- [ ] 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 `StreamReactionPicker` → `StreamMessageReactionPicker`
- [ ] Replace `reactionIcons` with `reactionIconResolver` in `StreamChatConfigurationData`
- [ ] Replace `MessageReactionsModal` with `ReactionDetailSheet.show()`
- [ ] Replace avatar `constraints` with `size` enum on all avatar widgets
- [ ] Rename `showOnlineStatus` → `showOnlineIndicator`
- [ ] Move avatar `onTap` callbacks to parent `GestureDetector` or `InkWell`
- [ ] Rename `StreamGroupAvatar` → `StreamUserAvatarGroup`; change `members` → `users`
- [ ] Rename `hideSendAsDm` → `canAlsoSendToChannelFromThread` (invert the value)
- [ ] Update `UnreadIndicatorButton` callback signatures (`onTap`, `onDismiss` → `onDismissTap`)
- [ ] 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 `onAttachmentTap` — `true` 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](https://github.com/GetStream/stream-chat-flutter/issues).


---

This page was last updated at 2026-04-23T18:43:03.986Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/flutter/v10/guides/migration_guide_10_0/](https://getstream.io/chat/docs/sdk/flutter/v10/guides/migration_guide_10_0/).