Configuration guide
Nexconn Chat UI exposes configuration objects for common UI changes and builder callbacks for full replacement of selected areas. The default pages work without configuration, and each config object can be applied only where your app needs different behavior.
Choose the right extension point
| Need | Use | Notes |
|---|---|---|
| Change built-in visibility, copy, colors, spacing, or default feature flags | Config objects | Examples: ChatPageConfig, MessageListConfig, MessageInputConfig, ChannelConfig. |
| Replace a specific visual region | Builder callbacks | Examples: channel item builders, message bubble builders, input reference preview builders. |
| Coordinate SDK state across pages | Providers | Examples: EngineProvider, ChannelProvider, ChatProvider, friend and group providers. |
| Own navigation, analytics, confirmation, or send policy | Page callbacks | Examples: onItemTap, onChannelAction, onBeforeSendMessage, onAfterSendMessage. |
| Fetch app-owned business data or request permissions | Host services and resolvers | Examples: profileProvider, mentionResolver, locationResolver, onPermissionRequest. |
When more than one layer could handle the same behavior, prefer the narrowest layer that owns the decision. Host callbacks take precedence over built-in defaults; builders replace only their visual region; providers should remain focused on Chat UI state and SDK orchestration.
Channel page
Use ChannelConfig to configure the channel list, channel item, app bar, and long-press menu.
ChannelPage(
config: ChannelConfig(
profileProvider: (channel, {message}) async {
final userId = message?.senderUserId ?? channel.channelId;
final profile = await accountRepository.loadUser(userId);
return ChatProfileInfo(
id: userId,
name: profile?.displayName ?? userId,
portraitUri: profile?.avatarUrl,
);
},
listConfig: ChannelListConfig(
pageSize: 50,
emptyText: 'No channels yet',
),
itemConfig: ChannelItemConfig(
avatarShape: ChannelAvatarShape.circle,
displayProfileResolver: (context, channel) async {
return ChannelDisplayProfile(displayName: channel.channelId);
},
),
),
);
Provide onItemTap when your app owns navigation. The callback receives (channel, index, context).
ChannelPage(
onItemTap: (channel, index, context) {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => ChatPage(channel: channel),
),
);
},
);
Channel navigation priority is onItemTap, then chatPageOpener, then ChannelListConfig.itemTapBehavior.
ChannelConfig.profileProvider is shared by built-in channel rows and the default ChatPage opened from the list. ChannelItemConfig.displayProfiles and displayProfileResolver still take priority for row-only overrides.
Chat page
Use ChatPageConfig to configure the app bar, background, message list, input area, bubble style, long-press menu, message hooks, and profile provider.
ChatPage(
channel: channel,
config: ChatPageConfig(
appBarConfig: ChatAppBarConfig(
titleResolver: (context, channel) async => channel.channelId,
showSearchButton: true,
),
messageListConfig: MessageListConfig(showAvatar: true),
inputConfig: MessageInputConfig(enableEmojiPanel: true),
),
);
Use message interceptors for send-time policy or analytics.
ChatPageConfig(
onBeforeSendMessage: (channel, params) async {
return true;
},
onAfterSendMessage: (channel, params, message, error) async {
debugPrint('Message send result: ${error?.code ?? 0}');
},
);
Message input
MessageInputConfig controls voice input, emoji, extension plugins, mention candidates, media picking, permission handling, and reference preview.
MessageInputConfig(
toolbarControls: const [
MessageInputBarControl.voice,
MessageInputBarControl.emoji,
MessageInputBarControl.extension,
],
extensionPlugins: const [
MessageInputExtensionPlugin.photo(),
MessageInputExtensionPlugin.file(),
],
);
For features that require host-app behavior, such as a custom location picker, inject a resolver or callback.
MessageInputExtensionPlugin.location(
title: 'Location',
locationResolver: (context, plugin) async {
return null;
},
);
On Android, the extension + button shares the trailing input slot with send: empty draft shows +, and non-empty draft shows send. Default media permissions are split by plugin type: photo/video request media-library access, camera requests camera only, and direct filming requests camera plus microphone.
Message bubbles
BubbleConfig controls sent and received message styles, corner radius, and avatar configuration. For custom message types, use customMessageBubbleBuilders.
ChatPage(
channel: channel,
config: ChatPageConfig(
bubbleConfig: const BubbleConfig(
borderRadius: 8,
avatarConfig: ChatAvatarConfig(shape: ChatAvatarShape.circle),
),
),
customMessageBubbleBuilders: {
MessageType.custom: (context, message, config) {
return Text('Custom message: ${message.messageId}');
},
},
);
Theme
Use NexconnThemeProvider for light, dark, or custom theme tokens.
final themeProvider = NexconnThemeProvider();
themeProvider.setMode(NexconnThemeMode.dark);
themeProvider.setCustomTheme(
NexconnThemeTokens.light.copyWith(
primaryColor: Colors.teal,
surfaceMutedColor: const Color(0xFFEFF3F5),
),
);
The default message input fill uses NexconnThemeTokens.surfaceMutedColor, including dark mode. A custom MessageInputConfig.decoration is respected as-is.
Profile and business data
Chat UI stays thin. User names, avatars, group data, friend operations, and group operations can be loaded from the Nexconn Chat SDK or from your application server through resolvers and operation callbacks.
ChannelConfig(
profileProvider: (channel, {message}) async {
final userId = message?.senderUserId ?? channel.channelId;
return ChatProfileInfo(id: userId, name: userId);
},
);