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)
backgroundSyncEnabled = true, // default
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'). The API is otherwise unchanged.
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 | backgroundCoreSurface |
appBackground | backgroundCoreApp |
borders | borderCoreDefault |
inputBackground | composerBg |
ownMessagesBackground | chatBgOutgoing |
otherMessagesBackground | chatBgIncoming |
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) | 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 | config.messageList.readCountEnabled |
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(
readCountEnabled = true,
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,ChannelViewModelFactory.isDraftMessageEnabled,MessagesViewModelFactory.isComposerDraftMessageEnabled
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
MessageContentFactory has been replaced by ChatComponentFactory, a comprehensive interface with ~185 overridable composable methods covering every UI component:
// v6
ChatTheme(messageContentFactory = MyContentFactory()) { ... }
// v7
class MyComponentFactory : ChatComponentFactory {
@Composable
override fun MessageContent(...) { ... }
@Composable
override fun ChannelListItemContent(...) { ... }
}
ChatTheme(componentFactory = MyComponentFactory()) { ... }Implement the ChatComponentFactory interface and override only the methods you need. Default implementations are provided for all 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 | SelectedReactionsMenu (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 icon, label, isDestructive, requiredCapability, 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 (64dp).
Other Changes
Crossfadeanimation removed for performance- Placeholder colors are deterministic based on user/channel ID (cycles through 5 color pairs)
Messages Screen
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 MessagesViewModelFactory (Compose) and MessageListViewModelFactory (XML) 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