User profiles
Nexconn Chat UI keeps profile display in the UI layer thin. Message bubbles, channel items, and profile pages can show names and avatars, but the app remains responsible for providing the data source that fits its account system.
Use two related APIs:
ChatProfileInfoandChatProfileProviderfor lightweight display data in chat UI surfaces.NexconnUserProfileProviderfor the full user profile page state, loading, refresh, and error handling.
Choose a profile source
| Source | Use when | Integration |
|---|---|---|
| App-owned account service | Your product already owns display names, avatars, roles, or privacy rules. | Provide ChannelConfig.profileProvider, ChatPageConfig.profileProvider, displayProfileResolver, or NexconnUserProfileProviderConfig resolvers. |
| Nexconn managed profile | You rely on SDK user profile APIs for the displayed profile data. | Use NexconnUserProfilePage or NexconnUserProfileProvider without custom resolvers. |
| Hybrid | You need SDK IDs and app-owned presentation data together. | Resolve app data first, then fall back to SDK profile or raw user ID. |
Display profiles in chat
ChatProfileInfo is the small model used by message bubbles, references, and combined-message previews.
class ChatProfileInfo {
final String id;
final String? name;
final String? portraitUri;
final String? extra;
const ChatProfileInfo({
required this.id,
this.name,
this.portraitUri,
this.extra,
});
}
Provide it through ChatPageConfig.profileProvider.
import 'package:ai_nexconn_chatui_plugin/ai_nexconn_chatui_plugin.dart';
ChatPage(
channel: DirectChannel('target_user_id'),
config: ChatPageConfig(
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,
);
},
),
);
The provider receives the active BaseChannel and, when available, the Message being rendered. In a group channel, use message.senderUserId to resolve the sender. In a direct channel, the channel ID is usually enough for the target user.
Share profiles between the channel list and chat page
When the channel list opens the built-in ChatPage, prefer ChannelConfig.profileProvider if the same resolver should drive channel rows, the default chat title, message senders, references, and combined-message previews.
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,
);
},
),
);
ChannelItemConfig.displayProfiles and ChannelItemConfig.displayProfileResolver still take priority for channel rows. Use them when the channel list needs a different display value from the chat page. Custom onItemTap or chatPageOpener integrations remain fully owned by your app.
Display profiles only in the channel list
Channel rows use ChannelDisplayProfile, because a row can represent a direct, group, system, open, community, or sub-channel entry.
ChannelPage(
config: ChannelConfig(
itemConfig: ChannelItemConfig(
displayProfileResolver: (context, channel) async {
if (channel.channelType == ChannelType.direct) {
final user = await accountRepository.loadUser(channel.channelId);
return ChannelDisplayProfile(
displayName: user?.displayName ?? channel.channelId,
avatarUrl: user?.avatarUrl,
);
}
return ChannelDisplayProfile(displayName: channel.channelId);
},
),
),
);
For static or preloaded data, ChannelItemConfig.displayProfiles can be used instead of an async resolver.
Full user profile page
NexconnUserProfileProvider powers NexconnUserProfilePage. If you do not provide custom resolvers, it falls back to the Nexconn SDK user APIs:
- Current user:
NCEngine.user.getMyUserProfile. - Other users:
NCEngine.user.getUserProfiles.
NexconnUserProfilePage(userId: 'target_user_id');
To plug in an app-owned profile service, configure the page with resolvers.
NexconnUserProfilePage(
userId: 'target_user_id',
config: NexconnUserProfilePageConfig(
providerConfig: NexconnUserProfileProviderConfig(
autoLoad: true,
profileResolver: (userId) {
return accountRepository.loadNexconnProfile(userId);
},
currentUserProfileResolver: (_) {
return accountRepository.loadCurrentNexconnProfile();
},
),
),
);
You can also own the provider directly when several widgets need the same state.
final provider = NexconnUserProfileProvider(
userId: 'target_user_id',
profileResolver: accountRepository.loadNexconnProfile,
autoLoad: true,
);
NexconnUserProfilePage(provider: provider);
Loading flow
Caching guidance
Cache profile data in the app layer when profile lookups come from your own backend. A simple cache avoids repeated requests while scrolling message lists.
class ProfileCache {
final Map<String, Future<ChatProfileInfo?>> _profiles = {};
Future<ChatProfileInfo?> get(String userId) {
return _profiles.putIfAbsent(userId, () async {
final user = await accountRepository.loadUser(userId);
if (user == null) {
return null;
}
return ChatProfileInfo(
id: userId,
name: user.displayName,
portraitUri: user.avatarUrl,
);
});
}
void invalidate(String userId) {
_profiles.remove(userId);
}
}
When a user changes their name or avatar, invalidate the affected cache entry and call the owning provider's refresh() method where applicable.