Skip to main content

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

NeedUseNotes
Change built-in visibility, copy, colors, spacing, or default feature flagsConfig objectsExamples: ChatPageConfig, MessageListConfig, MessageInputConfig, ChannelConfig.
Replace a specific visual regionBuilder callbacksExamples: channel item builders, message bubble builders, input reference preview builders.
Coordinate SDK state across pagesProvidersExamples: EngineProvider, ChannelProvider, ChatProvider, friend and group providers.
Own navigation, analytics, confirmation, or send policyPage callbacksExamples: onItemTap, onChannelAction, onBeforeSendMessage, onAfterSendMessage.
Fetch app-owned business data or request permissionsHost services and resolversExamples: 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.

Dart
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).

Dart
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.

Dart
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.

Dart
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.

Dart
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.

Dart
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.

Dart
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.

Dart
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.

Dart
ChannelConfig(
profileProvider: (channel, {message}) async {
final userId = message?.senderUserId ?? channel.channelId;
return ChatProfileInfo(id: userId, name: userId);
},
);