# Migrating from v6 to v7

This guide covers all breaking changes when migrating from **v6** to **v7** of the Stream Chat Android SDK. Version 7 consolidates modules, introduces a new design system, and redesigns several Compose UI components.

---

## What's New in v7

- Four modules consolidated — `offline`, `state`, `ui-utils`, and `ai-assistant` merged or removed
- Plugin system replaced with direct `ChatClient.Builder` configuration
- New `StreamDesign` theming system with `ChatUiConfig` for feature flags
- `ChatComponentFactory` — single interface for customizing all UI components (~185 methods)
- Attachment picker redesigned with mode-based configuration
- Emoji-based reactions via `ReactionResolver` (replaces icon-based `ReactionIconFactory`)
- Swipeable channel list items with self-describing `ChannelAction` model
- Slot-based message composer architecture
- Simplified avatar system with standardized sizes
- Optimized channel state management with active-window pagination model (replaces eager gap-filling)

---

## Dependencies

Four modules have been removed. Remove them from your `build.gradle`:

| Removed Module                     | Merged Into                                                                                        |
| ---------------------------------- | -------------------------------------------------------------------------------------------------- |
| `stream-chat-android-offline`      | `stream-chat-android-client`                                                                       |
| `stream-chat-android-state`        | `stream-chat-android-client`                                                                       |
| `stream-chat-android-ui-utils`     | `stream-chat-android-ui-common`                                                                    |
| `stream-chat-android-ai-assistant` | Removed (use [`stream-chat-android-ai`](https://github.com/GetStream/stream-chat-android-ai) repo) |

```kotlin
dependencies {
    // Keep these
    implementation("io.getstream:stream-chat-android-client:$version")
    implementation("io.getstream:stream-chat-android-compose:$version")
    // Or for XML Views:
    // implementation("io.getstream:stream-chat-android-ui-components:$version")

    // Remove these — no longer exist in v7
    implementation("io.getstream:stream-chat-android-offline:$version")       // REMOVE
    implementation("io.getstream:stream-chat-android-state:$version")         // REMOVE
    implementation("io.getstream:stream-chat-android-ui-utils:$version")      // REMOVE
    implementation("io.getstream:stream-chat-android-ai-assistant:$version")  // REMOVE
}
```

<admonition type="note">

If you depend on `stream-chat-android-compose` or `stream-chat-android-ui-components`, the `client` module is included transitively. You only need to add `client` explicitly if you use it standalone without a UI module.

</admonition>

---

## Client Setup

The plugin system has been replaced with direct configuration on `ChatClient.Builder`.

```kotlin {3-6}
// v6
val client = ChatClient.Builder(apiKey, context)
    .withPlugins(
        StreamOfflinePluginFactory(context),
        StreamStatePluginFactory(StatePluginConfig(), context),
    )
    .build()
```

```kotlin {3}
// v7
val client = ChatClient.Builder(apiKey, context)
    .config(ChatClientConfig())
    .build()
```

`config()` has sensible defaults. Calling it with no arguments gives you the same behavior as the v6 defaults.

### ChatClientConfig

`ChatClientConfig` replaces both `StreamOfflinePluginFactory` and `StreamStatePluginFactory(StatePluginConfig(...))`:

```kotlin
import io.getstream.chat.android.client.api.ChatClientConfig

ChatClientConfig(
    offlineEnabled = true,                              // default (was OfflineConfig.enabled)
    ignoredOfflineChannelTypes = emptySet(),            // default (was OfflineConfig.ignoredChannelTypes)
    userPresence = true,                                // default
    isAutomaticSyncOnReconnectEnabled = true,           // default
    syncMaxThreshold = TimeDuration.hours(12),          // default
    messageLimitConfig = MessageLimitConfig(),          // default
    useLegacyChannelLogic = false,                      // default (set true for v6 gap-filling behavior)
)
```

### Removed APIs

- `ChatClient.Builder.withPlugins()` — use `config()`
- `StreamOfflinePluginFactory`, `StreamStatePluginFactory`, `StatePluginConfig` — removed
- `PluginFactory` and `Plugin` interfaces — no longer public
- `ChatClient.Builder.withRepositoryFactoryProvider()` - removed
- `RepositoryFactory`, `RepositoryFactory.Provider` and all `-Repository` interfaces - no longer public

---

## Channel State Management

v7 changes how the SDK handles message loading when jumping to a specific message (e.g., a pinned message, a quoted message, or a search result).

### What changed

In v6, calling `loadMessagesAroundId` would load the target message page and then recursively fetch all intermediate pages between that page and the latest messages (the `fillTheGap` behavior). In v7, the SDK uses an **active-window model** — it loads only the requested page and lets the user paginate forward or backward by scrolling. This reduces unnecessary API calls and memory usage.

### Who is affected

<admonition type="info">

If you are using the Stream **UI Components** (Compose or XML), **no action is needed** — the message composer already handles this automatically.

</admonition>

This is a **breaking change** for developers who use the **state layer directly** (via `watchChannelAsState`, `loadMessagesAroundId`, and the `ChatClient` state extensions) to build a custom UI. Because the SDK no longer eagerly loads all messages up to the latest, it is possible for the user to be viewing a "middle page" where `ChannelState.endOfNewerMessages` is `false`. If a message is sent from this state, it will not appear alongside the latest messages unless you explicitly load them.

### How to migrate

Before sending a message, check `endOfNewerMessages` and call `loadNewestMessages` if the user is not on the latest page:

```kotlin
if (!endOfNewerMessages) {
    chatClient.loadNewestMessages(cid, messageLimit = 30).enqueue()
}
chatClient.sendMessage(channelType, channelId, message).enqueue()
```

For a complete example with Flow-based observation, see the [State Layer — Message Loading Behavior](/chat/docs/sdk/android/client/guides/state-layer-overview/#message-loading-behavior-jump-to-message) documentation.

### Opting out

To restore the v6 gap-filling behavior, enable the legacy channel logic:

```kotlin
ChatClient.Builder(apiKey, context)
    .config(ChatClientConfig(useLegacyChannelLogic = true))
    .build()
```

<admonition type="warning">

The legacy channel logic is provided for backward compatibility and may be removed in a future release.

</admonition>

---

## Package Relocations

Classes from removed modules have moved to new packages. Use the find-and-replace table below for bulk updates.

<details>
<summary>State module → Client module (full list)</summary>

| v6                                                                              | v7                                                                   |
| ------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `io.getstream.chat.android.state.plugin.config.StatePluginConfig`               | `io.getstream.chat.android.client.api.ChatClientConfig`              |
| `io.getstream.chat.android.state.plugin.state.global.GlobalState`               | `io.getstream.chat.android.client.api.state.GlobalState`             |
| `io.getstream.chat.android.state.plugin.state.StateRegistry`                    | `io.getstream.chat.android.client.api.state.StateRegistry`           |
| `io.getstream.chat.android.state.plugin.state.querychannels.QueryChannelsState` | `io.getstream.chat.android.client.api.state.QueryChannelsState`      |
| `io.getstream.chat.android.state.plugin.state.querythreads.QueryThreadsState`   | `io.getstream.chat.android.client.api.state.QueryThreadsState`       |
| `io.getstream.chat.android.state.plugin.state.channel.thread.ThreadState`       | `io.getstream.chat.android.client.api.state.ThreadState`             |
| `io.getstream.chat.android.state.plugin.state.channel.ChannelState`             | `io.getstream.chat.android.client.channel.state.ChannelState`        |
| `io.getstream.chat.android.state.plugin.state.querychannels.ChannelsStateData`  | `io.getstream.chat.android.client.api.state.ChannelsStateData`       |
| `io.getstream.chat.android.state.event.handler.chat.ChatEventHandler`           | `io.getstream.chat.android.client.api.event.ChatEventHandler`        |
| `io.getstream.chat.android.state.event.handler.chat.ChatEventHandlerFactory`    | `io.getstream.chat.android.client.api.event.ChatEventHandlerFactory` |
| `io.getstream.chat.android.state.event.handler.chat.EventHandlingResult`        | `io.getstream.chat.android.client.api.event.EventHandlingResult`     |
| `io.getstream.chat.android.state.event.handler.chat.BaseChatEventHandler`       | `io.getstream.chat.android.client.api.event.BaseChatEventHandler`    |
| `io.getstream.chat.android.state.event.handler.chat.DefaultChatEventHandler`    | `io.getstream.chat.android.client.api.event.DefaultChatEventHandler` |

Extension functions (`globalState`, `state`, `queryChannelsAsState`, `watchChannelAsState`, `loadOlderMessages`, `loadNewerMessages`, `loadNewestMessages`, `loadMessageById`, `loadMessagesAroundId`, `getRepliesAsState`):

```text
io.getstream.chat.android.state.extensions  →  io.getstream.chat.android.client.api.state
```

</details>

<details>
<summary>UI-Utils module → UI-Common module (full list)</summary>

| v6                                                   | v7                                                       |
| ---------------------------------------------------- | -------------------------------------------------------- |
| `io.getstream.chat.android.uiutils.model.MimeType`   | `io.getstream.chat.android.ui.common.model.MimeType`     |
| `io.getstream.chat.android.uiutils.util.ColorUtils`  | `io.getstream.chat.android.ui.common.utils.ColorUtil`    |
| `io.getstream.chat.android.uiutils.util.EmojiUtil`   | `io.getstream.chat.android.ui.common.utils.EmojiUtil`    |
| `io.getstream.chat.android.uiutils.extension.*`      | `io.getstream.chat.android.ui.common.utils.extensions.*` |
| `io.getstream.chat.android.uiutils.util.IntentUtils` | `io.getstream.chat.android.ui.common.utils.ContextUtils` |

<admonition type="note">

`ColorUtils` has been renamed to `ColorUtil` (without the 's'). Note that both `ColorUtil` and `ContextUtils` are now annotated `@InternalStreamChatApi` and are not intended for external consumption — if you were relying on these from `ui-utils`, copy the relevant logic into your own code or use a public alternative.

</admonition>

</details>

### Quick Find-and-Replace

Apply the most specific replacements first (longer package paths) to avoid partial matches:

```text
io.getstream.chat.android.state.plugin.config  →  io.getstream.chat.android.client.api
io.getstream.chat.android.state.plugin.state.global  →  io.getstream.chat.android.client.api.state
io.getstream.chat.android.state.plugin.state.querychannels  →  io.getstream.chat.android.client.api.state
io.getstream.chat.android.state.plugin.state.querythreads  →  io.getstream.chat.android.client.api.state
io.getstream.chat.android.state.plugin.state.channel.thread  →  io.getstream.chat.android.client.api.state
io.getstream.chat.android.state.plugin.state.channel  →  io.getstream.chat.android.client.channel.state
io.getstream.chat.android.state.plugin.state  →  io.getstream.chat.android.client.api.state
io.getstream.chat.android.state.event.handler.chat  →  io.getstream.chat.android.client.api.event
io.getstream.chat.android.state.extensions  →  io.getstream.chat.android.client.api.state
io.getstream.chat.android.uiutils  →  io.getstream.chat.android.ui.common
```

---

## Theming

The theming system has been completely rebuilt. Individual theme classes are replaced by a unified `StreamDesign` namespace, and behavioral feature flags are grouped in `ChatUiConfig`.

### StreamDesign.Colors

`StreamColors` is replaced by `StreamDesign.Colors`:

```kotlin
// v6
val colors = StreamColors.defaultColors()
ChatTheme(colors = colors) { ... }

// v7
val colors = StreamDesign.Colors.default()        // light
val darkColors = StreamDesign.Colors.defaultDark() // dark
ChatTheme(colors = colors) { ... }
```

<details>
<summary>Color property mapping</summary>

| v6                 | v7                             |
| ------------------ | ------------------------------ |
| `primaryAccent`    | `accentPrimary`                |
| `errorAccent`      | `accentError`                  |
| `textHighEmphasis` | `textPrimary`                  |
| `textLowEmphasis`  | `textSecondary`                |
| `disabled`         | `textDisabled`                 |
| `barsBackground`   | `backgroundCoreSurfaceDefault` |
| `appBackground`    | `backgroundCoreApp`            |
| `borders`          | `borderCoreDefault`            |

`inputBackground`, `ownMessagesBackground`, and `otherMessagesBackground` no longer map to single public color tokens. Composer and message-bubble surfaces are now derived internally from the `brand` and `chrome` color scales — customize those scales via `StreamDesign.Colors` to influence these surfaces.

</details>

### StreamDesign.Typography

`StreamTypography` is replaced by `StreamDesign.Typography`:

```kotlin
// v6
val typography = StreamTypography.defaultTypography(fontFamily = myFont)

// v7
val typography = StreamDesign.Typography.default(fontFamily = myFont)
```

<details>
<summary>Typography mapping</summary>

| v6             | v7                  |
| -------------- | ------------------- |
| `title1`       | `headingLarge`      |
| `title3`       | `headingMedium`     |
| `title3Bold`   | `headingSmall`      |
| `body`         | `bodyDefault`       |
| `bodyBold`     | `bodyEmphasis`      |
| `footnote`     | `captionDefault`    |
| `footnoteBold` | `captionEmphasis`   |
| `captionBold`  | `metadataEmphasis`  |
| _(new)_        | `headingExtraSmall` |
| _(new)_        | `metadataDefault`   |
| _(new)_        | `numericSmall`      |
| _(new)_        | `numericMedium`     |
| _(new)_        | `numericLarge`      |
| _(new)_        | `numericExtraLarge` |

</details>

### Removed Theme Classes

| Removed        | Replacement                       |
| -------------- | --------------------------------- |
| `StreamDimens` | `StreamTokens` (internal)         |
| `StreamShapes` | `StreamTokens.radius*` (internal) |

<admonition type="caution">

`StreamTokens` is internal API. If you referenced `StreamDimens` or `StreamShapes` directly, use the token values via `ChatTheme.colors` and `ChatTheme.typography` instead.

</admonition>

### ChatTheme Changes

```kotlin
// v7
ChatTheme(
    isInDarkMode = isSystemInDarkTheme(),
    colors = StreamDesign.Colors.default(),
    typography = StreamDesign.Typography.default(),
    config = ChatUiConfig(),
    componentFactory = object : ChatComponentFactory {},
    reactionResolver = ReactionResolver.defaultResolver(),
) { content() }
```

| Removed Parameter                                          | Replacement                                                                                       |
| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `dimens`                                                   | Removed (internal `StreamTokens`)                                                                 |
| `shapes`                                                   | Removed (internal `StreamTokens.radius*`)                                                         |
| `reactionIconFactory`                                      | `reactionResolver`                                                                                |
| `messageContentFactory`                                    | `componentFactory` (`ChatComponentFactory`)                                                       |
| `autoTranslationEnabled`                                   | `config.translation.enabled`                                                                      |
| `isComposerLinkPreviewEnabled`                             | `config.composer.linkPreviewEnabled`                                                              |
| `videoThumbnailsEnabled`                                   | `config.messageList.videoThumbnailsEnabled`                                                       |
| `readCountEnabled`                                         | Removed                                                                                           |
| `userPresence`                                             | Removed (online status always shown; control via `showIndicator` on `UserAvatar`/`ChannelAvatar`) |
| `ownMessageTheme`, `otherMessageTheme`                     | Removed                                                                                           |
| `messageDateSeparatorTheme`, `messageUnreadSeparatorTheme` | Removed                                                                                           |
| `attachmentPickerTheme`                                    | Removed                                                                                           |
| `attachmentsPickerTabFactories`                            | `config.attachmentPicker.modes`                                                                   |

### ChatUiConfig

Feature flags that were scattered across `ChatTheme` parameters are now grouped in `ChatUiConfig`:

```kotlin
ChatTheme(
    config = ChatUiConfig(
        translation = TranslationConfig(enabled = true),
        messageList = MessageListConfig(
            videoThumbnailsEnabled = true,
        ),
        composer = ComposerConfig(
            audioRecordingEnabled = true,
            linkPreviewEnabled = false,
        ),
        channelList = ChannelListConfig(
            swipeActionsEnabled = true,
            muteIndicatorPosition = MuteIndicatorPosition.InlineTitle,
        ),
        attachmentPicker = AttachmentPickerConfig(
            useSystemPicker = true,
            modes = listOf(
                GalleryPickerMode(),
                FilePickerMode(),
                CameraPickerMode(),
                PollPickerMode(),
                CommandPickerMode,
            ),
        ),
    ),
) { content() }
```

### Changed Defaults

Several feature flags now default to `true` (previously `false`):

- **Translation**: `TranslationConfig.enabled`, `TranslationConfig.showOriginalEnabled`, `ChatUI.autoTranslationEnabled`, `ChatUI.showOriginalTranslationEnabled`, `NotificationConfig.autoTranslationEnabled`
- **Audio recording**: `ComposerConfig.audioRecordingEnabled` (audio recording button is visible and enabled by default)
- **Draft messages**: `ChatUI.draftMessagesEnabled`, `ChannelListViewModelFactory.draftMessagesEnabled` (Compose channel list), `ComposerOptions.draftMessagesEnabled` (Compose messages, via `ChannelViewModelFactory.composerOptions`)

Additionally, `ComposerConfig.audioRecordingSendOnComplete` now defaults to `false` (recordings are attached for manual send instead of auto-sending).

If you previously relied on these features being disabled, explicitly set them to `false`.

### ChatComponentFactory

A key design principle in v7 is consolidating UI customization into a single place. In v6, customization was spread across composable slot parameters, individual callback params, styling params, and factory classes. In v7, all of this is routed through `ChatComponentFactory`: if you need to customize a component, your best bet is to override the corresponding function in the factory.

`ChatComponentFactory` replaces `MessageContentFactory`, composable slot parameters (`itemContent`, `loadingContent`, `emptyContent`, `channelContent`, etc.), and many pass-through params that existed on components like `MessageList`, `ChannelList`, `ThreadList`, and `PinnedMessageList`.

```kotlin
// v6
ChatTheme(messageContentFactory = MyContentFactory()) { ... }

// v7
ChatTheme(componentFactory = MyComponentFactory()) { ... }
```

Implement the interface and override only the methods you need (default implementations are provided for all methods).

#### Customizing a component's appearance

For example, rendering a custom channel item with only the avatar and name:

```kotlin
// v6
ChannelList(
    channelContent = { channelItem ->
        Row {
            ChannelAvatar(...)
            Text(channelName)
        }
    }
)

// v7: override the factory method
object MyFactory : ChatComponentFactory {
    @Composable
    override fun LazyItemScope.ChannelListItemContent(params: ChannelListItemContentParams) {
        Row {
            ChannelAvatar(channel = params.channelItem.channel, currentUser = params.currentUser)
            Text(ChatTheme.channelNameFormatter.formatChannelName(params.channelItem.channel, params.currentUser))
        }
    }

    // Any other override you need...
}

ChatTheme(componentFactory = MyFactory) {
    ChannelList(viewModel = listViewModel)
}
```

#### Customizing callbacks

For example, overriding click handlers on channel items:

```kotlin
// v6
ChannelList(
    channelContent = { channelItem ->
        ChannelItem(
            channelItem = channelItem,
            currentUser = user,
            onChannelClick = { /* custom click */ },
            onChannelLongClick = { /* custom long click */ },
        )
    }
)

// v7: override in the factory
object MyFactory : ChatComponentFactory {
    @Composable
    override fun LazyItemScope.ChannelListItemContent(params: ChannelListItemContentParams) {
        ChannelItem(
            channelItem = params.channelItem,
            currentUser = params.currentUser,
            onChannelClick = { /* custom click */ },
            onChannelLongClick = { /* custom long click */ },
        )
    }
}
```

Common callbacks like `onChannelClick` and `onChannelLongClick` can also be passed directly to `ChannelList`, which forwards them to the factory via the params object.

See the [Component Factory](/chat/docs/sdk/android/compose/general-customization/component-factory/) documentation for the full list of overridable methods.

### CompositionLocals

```kotlin
// v6
LocalStreamColors.current
LocalStreamDimens.current
LocalStreamTypography.current
LocalStreamShapes.current

// v7 — access via ChatTheme object
ChatTheme.colors            // StreamDesign.Colors
ChatTheme.typography        // StreamDesign.Typography
ChatTheme.config            // ChatUiConfig
ChatTheme.componentFactory  // ChatComponentFactory
ChatTheme.reactionResolver  // ReactionResolver
```

---

## Attachment Picker

The attachment picker has been completely redesigned with a mode-based architecture replacing the tab factory pattern.

### Key Renames

| v6                                     | v7                                 |
| -------------------------------------- | ---------------------------------- |
| `AttachmentsPicker`                    | `AttachmentPicker`                 |
| `AttachmentsPickerMode` (sealed class) | `AttachmentPickerMode` (interface) |
| `AttachmentsPickerMode.Images`         | `GalleryPickerMode()`              |
| `AttachmentsPickerMode.Files`          | `FilePickerMode()`                 |
| `AttachmentsPickerMode.MediaCapture`   | `CameraPickerMode()`               |
| `AttachmentsPickerMode.Poll(...)`      | `PollPickerMode(...)`              |
| _(none)_                               | `CommandPickerMode`                |

Each mode now carries its own configuration (multi-select, media type, capture mode).

### Tab Factory Removal

```kotlin
// v6 — factory-based
AttachmentsPicker(
    tabFactories = listOf(
        AttachmentsPickerImagesTabFactory(),
        AttachmentsPickerFilesTabFactory(),
        AttachmentsPickerMediaCaptureTabFactory(),
    ),
)

// v7 — mode-based via ChatUiConfig
ChatTheme(
    config = ChatUiConfig(
        attachmentPicker = AttachmentPickerConfig(
            useSystemPicker = true,
            modes = listOf(GalleryPickerMode(), FilePickerMode(), CameraPickerMode()),
        ),
    ),
) { ... }
```

The following classes no longer exist: `AttachmentsPickerTabFactory`, `AttachmentsPickerImagesTabFactory`, `AttachmentsPickerFilesTabFactory`, `AttachmentsPickerMediaCaptureTabFactory`.

### System Picker vs. In-App Picker

v7 introduces `useSystemPicker` (default `true`) that uses native OS pickers instead of the in-app picker UI. When `false`, it shows the traditional in-app tabs with media grid and file list — this requires adding `READ_MEDIA_IMAGES`/`READ_MEDIA_VIDEO` permissions to your manifest.

### AttachmentPickerActions

A new `AttachmentPickerActions` class consolidates all picker interaction handlers:

```kotlin
AttachmentPicker(
    attachmentsPickerViewModel = pickerViewModel,
    messageMode = MessageMode.Normal,
    actions = AttachmentPickerActions.defaultActions(pickerViewModel, composerViewModel),
)
```

---

## Reactions

Reactions are now emoji-only by default. The icon-based reaction system has been replaced by `ReactionResolver`.

### ReactionResolver

| v6                                          | v7                                                                                       |
| ------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `ReactionIconFactory`                       | `ReactionResolver`                                                                       |
| `ReactionPushEmojiFactory`                  | `ReactionResolver`                                                                       |
| `ReactionOptionItemState(painter: Painter)` | `ReactionOptionItemState(emojiCode: String?)`                                            |
| `UserReactionItemState(painter: Painter)`   | `UserReactionItemState(emojiCode: String?)`                                              |
| `SelectedReactionsMenu`                     | `ReactionsMenu` (redesigned, uses `ReactionsMenuViewModel` with cursor-based pagination) |
| `ReactionOptions`                           | Removed                                                                                  |
| `ReactionOptionItem`                        | Removed                                                                                  |
| `AdaptiveMessageReactions`                  | Removed                                                                                  |

```kotlin
// v6
ChatTheme(reactionIconFactory = ReactionIconFactory.defaultFactory()) { ... }

// v7
ChatTheme(reactionResolver = ReactionResolver.defaultResolver()) { ... }
```

### Custom Reactions

```kotlin
val customResolver = DefaultReactionResolver(
    emojiMapping = linkedMapOf(
        "like" to "\uD83D\uDC4D",
        "love" to "\u2764\uFE0F",
        "fire" to "\uD83D\uDD25",
    ),
    defaultReactions = listOf("like", "love", "fire"),
)

ChatTheme(reactionResolver = customResolver) { ... }
```

<admonition type="note">

If you had custom `Painter`-based reactions via `ReactionIconFactory`, you'll need to switch to emoji characters. The v7 reaction system is emoji-only.

</admonition>

### New: MessageReactionItemState

```kotlin
// v7 — new class for inline reaction display
data class MessageReactionItemState(
    val type: String,
    val emoji: String?,
    val count: Int,
)
```

Used by `ClusteredMessageReactions` and `SegmentedMessageReactions` (which replace `AdaptiveMessageReactions`).

---

## Channel List

The channel list has been redesigned with swipe-to-action support and a self-describing action model.

### SwipeableChannelItem

Swipe actions are enabled by default. Control via `ChatUiConfig`:

```kotlin
ChatTheme(
    config = ChatUiConfig(
        channelList = ChannelListConfig(swipeActionsEnabled = true),
    ),
) { ... }
```

Customize swipe actions via `ChatComponentFactory.ChannelSwipeActions`. `SwipeActionItem` supports three styles: `Primary` (blue), `Secondary` (gray), `Destructive` (red).

### ChannelAction Replaces ChannelOptionState

| v6                                               | v7                                                                                                                            |
| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| `ChannelOptionState(title, iconPainter, action)` | `ChannelAction` interface — self-describing with `channel`, `icon`, `label`, `isDestructive`, `confirmationPopup`, `onAction` |

Built-in actions: `ViewInfo`, `MuteChannel`/`UnmuteChannel`, `PinChannel`/`UnpinChannel`, `ArchiveChannel`/`UnarchiveChannel`, `LeaveGroup`, `DeleteConversation`, `MuteUser`/`UnmuteUser`, `BlockUser`/`UnblockUser`.

```kotlin
// v6
val options = listOf(
    ChannelOptionState(
        title = R.string.mute,
        iconPainter = painterResource(R.drawable.ic_mute),
        action = Mute(channel),
    ),
)

// v7
val actions = listOf(
    MuteChannel(
        channel = channel,
        label = stringResource(R.string.mute),
        onAction = { viewModel.muteChannel(channel) },
    ),
)
```

### ChannelItemState

`ChannelItemState` now includes `isSelected: Boolean` (default `false`). Set automatically when a channel is long-pressed to show the options menu.

### Other Changes

- **Search mode**: `SearchMode` enum (`None`, `Channels`, `Messages`) replaces boolean flags
- **Loading shimmer**: `ChannelListLoadingItem` adds skeleton placeholders during load
- **Mute indicator position**: Configurable via `ChannelListConfig.muteIndicatorPosition`

---

## Message Composer

The composer has been restructured into a slot-based layout.

### Slot Architecture

| Slot     | Component                        | Purpose                                       |
| -------- | -------------------------------- | --------------------------------------------- |
| Leading  | `MessageComposerLeadingContent`  | Attachment button                             |
| Center   | `MessageComposerInput`           | Text input, quote/edit indicator, attachments |
| Trailing | `MessageComposerTrailingContent` | Empty by default (extension point)            |

The center input is further divided into `InputLeadingContent` (command chip), `InputCenterContent` (text field), `InputCenterBottomContent` ("Also send to channel" checkbox), and `InputTrailingContent` (send/save/record button).

### Parameter Changes

| Removed               | Replacement                                                       |
| --------------------- | ----------------------------------------------------------------- |
| `mentionPopupContent` | Built-in `SuggestionsMenu` (customize via `ChatComponentFactory`) |
| `commandPopupContent` | Built-in `SuggestionsMenu` (customize via `ChatComponentFactory`) |
| `headerContent`       | Removed (use `ChatComponentFactory` overrides)                    |
| `footerContent`       | Removed (use `ChatComponentFactory` overrides)                    |
| `integrations`        | `onAttachmentsClick` callback                                     |

### Renamed Components

| v6                      | v7                                                                    |
| ----------------------- | --------------------------------------------------------------------- |
| `MentionSuggestionItem` | `UserSuggestionItem` (internal, customize via `ChatComponentFactory`) |
| `MentionSuggestionList` | `UserSuggestionList` (internal, customize via `ChatComponentFactory`) |
| `SuggestionList`        | `SuggestionsMenu` (internal)                                          |

### Inline Edit Indicator

v7 adds a visual inline edit indicator that appears automatically when `action is Edit` in the composer state. Shows "Edit message" with a preview and cancel button.

### Audio Recording

Audio recording UI components are now internal. Enable via `ChatUiConfig`:

```kotlin
ChatTheme(
    config = ChatUiConfig(
        composer = ComposerConfig(
            audioRecordingEnabled = true,
            audioRecordingSendOnComplete = false,
        ),
    ),
) { ... }
```

---

## Avatars

The avatar system removes several standalone composables and consolidates them.

| Removed          | Replacement                                 |
| ---------------- | ------------------------------------------- |
| `GroupAvatar`    | `ChannelAvatar` (handles groups internally) |
| `ImageAvatar`    | `Avatar` (internal)                         |
| `InitialsAvatar` | `UserAvatarPlaceholder` (internal)          |
| `UserAvatarRow`  | `UserAvatarStack` (internal)                |

`UserAvatar` and `ChannelAvatar` remain the public API. Use these instead of the removed low-level components.

### Migration Examples

```kotlin
// v6
UserAvatar(user = user, modifier = Modifier.size(40.dp), showOnlineIndicator = true)

// v7
UserAvatar(user = user, modifier = Modifier.size(AvatarSize.Large), showIndicator = true)
```

```kotlin
// v6
GroupAvatar(users = members.map { it.user }, modifier = Modifier.size(48.dp))

// v7
ChannelAvatar(channel = channel, currentUser = currentUser, modifier = Modifier.size(AvatarSize.ExtraLarge))
```

### Standardized Sizes

v7 introduces `AvatarSize` constants: `ExtraSmall` (20dp), `Small` (24dp), `Medium` (32dp), `Large` (40dp), `ExtraLarge` (48dp), `ExtraExtraLarge` (80dp).

### Other Changes

- Placeholder colors are deterministic based on user/channel ID (cycles through 5 color pairs)

---

## Messages Screen

### Renamed Screens & Factories

The messages screen and its associated ViewModel factories have been renamed to align with the new `ChannelScreen` naming:

| v6                                       | v7                                   |
| ---------------------------------------- | ------------------------------------ |
| `MessagesScreen` (Compose)               | `ChannelScreen` (Compose)            |
| `MessagesViewModelFactory` (Compose)     | `ChannelViewModelFactory` (Compose)  |
| `MessageListViewModelFactory` (XML/View) | `ChannelViewModelFactory` (XML/View) |

The renamed Compose factory lives at `io.getstream.chat.android.compose.viewmodel.messages.ChannelViewModelFactory`. The renamed XML/View factory lives at `io.getstream.chat.android.ui.viewmodel.messages.ChannelViewModelFactory`. In the Compose factory, message list and composer settings have been grouped into `MessageListOptions` and `ComposerOptions` (see [ChannelViewModelFactory Configuration](/chat/docs/sdk/android/compose/message-components/channel-screen/#channelviewmodelfactory-configuration)).

### New: onChannelAvatarClick

```kotlin
ChannelScreen(
    viewModelFactory = factory,
    onChannelAvatarClick = { channel ->
        navController.navigate("channel/${channel.cid}/info")
    },
)
```

When `null` (default), the channel avatar in the header is not clickable.

### Typing Indicator

The typing indicator has moved from a separate overlay into the message list as an inline item. Remove any separate `TypingIndicator` component — it appears automatically.

### Removed: DeletedMessageVisibility

`DeletedMessageVisibility` and the `deletedMessageVisibility` parameter on `ChannelViewModelFactory` (formerly `MessagesViewModelFactory` in Compose, `MessageListViewModelFactory` in XML — see [Renamed Screens & Factories](#renamed-screens--factories)) have been removed. Deleted messages are now always visible as placeholders.

This was removed because hiding deleted messages could break message pagination in rare edge cases. If you were passing `deletedMessageVisibility` to any ViewModel factory, remove that parameter.

### Visual Changes

- **Date separators**: Rounded pill style with `metadataEmphasis` typography
- **Unread separator**: Same pill style, visually distinguishes new messages
- **System messages**: Redesigned bubble
- **Deleted messages**: Updated design
- **Start of channel**: New "beginning of conversation" indicator at the top

---

## CDN Configuration

All deprecated CDN-related classes and interfaces have been removed in v7. The unified `CDN` interface is now the only way to customize how the SDK loads files and images from your CDN.

### Removed API → V7 Replacement

| Removed API                                                | V7 Replacement                                                                          |
| ---------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| `ImageHeadersProvider`                                     | `CDN.imageRequest()` — return headers in `CDNRequest.headers`                           |
| `AsyncImageHeadersProvider`                                | `CDN.imageRequest()` — the CDN method is already `suspend`                              |
| `VideoHeadersProvider`                                     | `CDN.fileRequest()` — return headers in `CDNRequest.headers`                            |
| `ImageAssetTransformer`                                    | `CDN.imageRequest()` — return transformed URL in `CDNRequest.url`                       |
| `DownloadAttachmentUriGenerator`                           | `CDN.imageRequest()` / `CDN.fileRequest()` — return transformed URL in `CDNRequest.url` |
| `DownloadRequestInterceptor`                               | `CDN.imageRequest()` / `CDN.fileRequest()` — return headers in `CDNRequest.headers`     |
| `ChatClient.Builder.shareFileDownloadRequestInterceptor()` | `CDN.fileRequest()` — return URL / headers in `CDNRequest`                              |
| `AttachmentDocumentActivity`                               | Documents always open via external apps                                                 |
| `useDocumentGView` (`ChatUI` / `ChatTheme`)                | Removed — documents always open via external apps                                       |

### Migration Example

**Before** (v6) — scattered across multiple locations:

```kotlin
// Compose: image loading headers, download URI generation, and download request interception
ChatTheme(
    imageHeadersProvider = object : ImageHeadersProvider {
        override fun getImageRequestHeaders(url: String) =
            mapOf("Authorization" to "Bearer $token")
    },
    downloadAttachmentUriGenerator = DownloadAttachmentUriGenerator { attachment ->
        Uri.parse(signingService.sign(attachment.assetUrl ?: attachment.imageUrl))
    },
    downloadRequestInterceptor = DownloadRequestInterceptor { request ->
        request.addRequestHeader("Authorization", "Bearer $token")
    },
) {
    // Your chat UI content
}

// XML: image loading headers, video headers, download URI generation, and download request interception
ChatUI.imageHeadersProvider = object : ImageHeadersProvider {
    override fun getImageRequestHeaders(url: String) =
        mapOf("Authorization" to "Bearer $token")
}
ChatUI.videoHeadersProvider = object : VideoHeadersProvider {
    override fun getVideoRequestHeaders(url: String) =
        mapOf("Authorization" to "Bearer $token")
}
ChatUI.downloadAttachmentUriGenerator = DownloadAttachmentUriGenerator { attachment ->
    Uri.parse(signingService.sign(attachment.assetUrl ?: attachment.imageUrl))
}
ChatUI.downloadRequestInterceptor = DownloadRequestInterceptor { request ->
    request.addRequestHeader("Authorization", "Bearer $token")
}
```

**After** (v7) — single configuration point:

```kotlin
ChatClient.Builder("apiKey", context)
    .cdn(object : CDN {
        override suspend fun imageRequest(url: String) = CDNRequest(
            url = signingService.sign(url),
            headers = mapOf("Authorization" to "Bearer $token")
        )
        override suspend fun fileRequest(url: String) = CDNRequest(
            url = signingService.sign(url),
            headers = mapOf("Authorization" to "Bearer $token")
        )
    })
    .build()
```

For full documentation on the `CDN` interface, see [Custom CDN](/chat/docs/sdk/android/client/guides/custom-cdn).

---

## Migration Checklist

1. **Update dependencies** — remove `offline`, `state`, `ui-utils`, `ai-assistant` artifacts
2. **Update client setup** — replace `withPlugins(...)` with `config(...)`
3. **Update imports** — fix all `io.getstream.chat.android.state.*` and `io.getstream.chat.android.offline.*` imports (see [Package Relocations](#package-relocations))
4. **Update theming** — migrate from `StreamColors`/`StreamDimens`/`StreamTypography`/`StreamShapes` to `StreamDesign`
5. **Update ChatTheme** — pass `config` for feature flags, `reactionResolver` for reactions, `componentFactory` for UI customization
6. **Update attachment picker** — replace `AttachmentsPicker` with `AttachmentPicker`, remove tab factories, configure modes via `ChatUiConfig`
7. **Update reaction code** — replace `ReactionIconFactory` with `ReactionResolver`, update state models from `painter` to `emojiCode`
8. **Update channel list** — handle new swipe actions, replace `ChannelOptionState` with `ChannelAction` subclasses
9. **Update avatar usage** — replace `GroupAvatar`/`ImageAvatar`/`InitialsAvatar` with `UserAvatar`/`ChannelAvatar`
10. **Review channel state usage** — if you use `loadMessagesAroundId` with a custom UI, ensure you call `loadNewestMessages` when sending messages from a non-latest page (see [Channel State Management](#channel-state-management))
11. **Update CDN configuration** — replace old CDN APIs (`ImageHeadersProvider`, `AsyncImageHeadersProvider` `VideoHeadersProvider`, `DownloadAttachmentUriGenerator`, `DownloadRequestInterceptor`, `ImageAssetTransformer`) with the unified `CDN` interface on `ChatClient.Builder`; remove `useDocumentGView` references
12. **Build and test** — verify compilation and run UI tests


---

This page was last updated at 2026-04-17T17:33:33.079Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/android/migration-guides/migrating-from-v6-to-v7/](https://getstream.io/chat/docs/sdk/android/migration-guides/migrating-from-v6-to-v7/).