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
}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, andai-assistantmerged or removed - Plugin system replaced with direct
ChatClient.Builderconfiguration - New
StreamDesigntheming system withChatUiConfigfor 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-basedReactionIconFactory) - Swipeable channel list items with self-describing
ChannelActionmodel - 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 repo) |
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.
Client Setup
The plugin system has been replaced with direct configuration on ChatClient.Builder.
// v6
val client = ChatClient.Builder(apiKey, context)
.withPlugins(
StreamOfflinePluginFactory(context),
StreamStatePluginFactory(StatePluginConfig(), context),
)
.build()// 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(...)):
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()— useconfig()StreamOfflinePluginFactory,StreamStatePluginFactory,StatePluginConfig— removedPluginFactoryandPlugininterfaces — no longer publicChatClient.Builder.withRepositoryFactoryProvider()- removedRepositoryFactory,RepositoryFactory.Providerand all-Repositoryinterfaces - 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
If you are using the Stream UI Components (Compose or XML), no action is needed — the message composer already handles this automatically.
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:
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 documentation.
Opting out
To restore the v6 gap-filling behavior, enable the legacy channel logic:
ChatClient.Builder(apiKey, context)
.config(ChatClientConfig(useLegacyChannelLogic = true))
.build()The legacy channel logic is provided for backward compatibility and may be removed in a future release.
Package Relocations
Classes from removed modules have moved to new packages. Use the find-and-replace table below for bulk updates.
State module → Client module (full list)
| 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):
io.getstream.chat.android.state.extensions → io.getstream.chat.android.client.api.stateUI-Utils module → UI-Common module (full list)
| 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 |
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.
Quick Find-and-Replace
Apply the most specific replacements first (longer package paths) to avoid partial matches:
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.commonTheming
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:
// v6
val colors = StreamColors.defaultColors()
ChatTheme(colors = colors) { ... }
// v7
val colors = StreamDesign.Colors.default() // light
val darkColors = StreamDesign.Colors.defaultDark() // dark
ChatTheme(colors = colors) { ... }Color property mapping
| 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.
StreamDesign.Typography
StreamTypography is replaced by StreamDesign.Typography:
// v6
val typography = StreamTypography.defaultTypography(fontFamily = myFont)
// v7
val typography = StreamDesign.Typography.default(fontFamily = myFont)Typography mapping
| 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 |
Removed Theme Classes
| Removed | Replacement |
|---|---|
StreamDimens | StreamTokens (internal) |
StreamShapes | StreamTokens.radius* (internal) |
StreamTokens is internal API. If you referenced StreamDimens or StreamShapes directly, use the token values via ChatTheme.colors and ChatTheme.typography instead.
ChatTheme Changes
// 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:
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, viaChannelViewModelFactory.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.
// 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:
// 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:
// 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 documentation for the full list of overridable methods.
CompositionLocals
// 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 // ReactionResolverAttachment 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
// 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:
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 |
// v6
ChatTheme(reactionIconFactory = ReactionIconFactory.defaultFactory()) { ... }
// v7
ChatTheme(reactionResolver = ReactionResolver.defaultResolver()) { ... }Custom Reactions
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) { ... }If you had custom Painter-based reactions via ReactionIconFactory, you'll need to switch to emoji characters. The v7 reaction system is emoji-only.
New: MessageReactionItemState
// 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:
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.
// 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:
SearchModeenum (None,Channels,Messages) replaces boolean flags - Loading shimmer:
ChannelListLoadingItemadds 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:
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
// v6
UserAvatar(user = user, modifier = Modifier.size(40.dp), showOnlineIndicator = true)
// v7
UserAvatar(user = user, modifier = Modifier.size(AvatarSize.Large), showIndicator = true)// 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).
New: onChannelAvatarClick
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) 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
metadataEmphasistypography - 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:
// 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:
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.
Migration Checklist
- Update dependencies — remove
offline,state,ui-utils,ai-assistantartifacts - Update client setup — replace
withPlugins(...)withconfig(...) - Update imports — fix all
io.getstream.chat.android.state.*andio.getstream.chat.android.offline.*imports (see Package Relocations) - Update theming — migrate from
StreamColors/StreamDimens/StreamTypography/StreamShapestoStreamDesign - Update ChatTheme — pass
configfor feature flags,reactionResolverfor reactions,componentFactoryfor UI customization - Update attachment picker — replace
AttachmentsPickerwithAttachmentPicker, remove tab factories, configure modes viaChatUiConfig - Update reaction code — replace
ReactionIconFactorywithReactionResolver, update state models frompaintertoemojiCode - Update channel list — handle new swipe actions, replace
ChannelOptionStatewithChannelActionsubclasses - Update avatar usage — replace
GroupAvatar/ImageAvatar/InitialsAvatarwithUserAvatar/ChannelAvatar - Review channel state usage — if you use
loadMessagesAroundIdwith a custom UI, ensure you callloadNewestMessageswhen sending messages from a non-latest page (see Channel State Management) - Update CDN configuration — replace old CDN APIs (
ImageHeadersProvider,AsyncImageHeadersProviderVideoHeadersProvider,DownloadAttachmentUriGenerator,DownloadRequestInterceptor,ImageAssetTransformer) with the unifiedCDNinterface onChatClient.Builder; removeuseDocumentGViewreferences - Build and test — verify compilation and run UI tests