Flutter即时通讯UI组件插件em_chat_uikit的使用

Flutter即时通讯UI组件插件em_chat_uikit的使用

开发环境

environment:
  sdk: '>=3.0.0 <4.0.0'
  flutter: '>=3.19.0'
  • iOS: 12+
  • Android: minSDKVersion 24

安装

flutter pub add em_chat_uikit

结构

.
├── chat_uikit.dart                                             // 库文件
├── chat_uikit_alphabet_sort_helper.dart                        // 用于纠正联系人字母顺序的工具
├── chat_uikit_defines.dart                                     // UIKit 处理定义类
├── chat_uikit_emoji_data.dart                                  // 消息表情数据类
├── chat_uikit_localizations.dart                               // 国际化工具类
├── chat_uikit_service                                          // 聊天 SDK 包装类的二次封装,用于将不合规功能适配到 UIKit 功能
├── chat_uikit_settings.dart                                    // 用于设置功能的类,用于打开或关闭某些功能
├── chat_uikit_time_formatter.dart                              // 用于设置显示时间格式的工具
├── provider                                                    // 用户属性工具
│   ├── chat_uikit_profile.dart                                 // 用户属性对象,包括用户的头像、昵称和备注
│   └── chat_uikit_provider.dart                                // 用户属性提供者,用于在 UIKit 中提供用户属性数据
├── sdk_service                                                 // 聊天 SDK 包装,用于包装聊天 SDK 可供开发者使用的 API。UIKit 与包装类交互,而不是直接调用聊天 SDK 的 API
├── tools                                                       // 内部工具类
│   ├── chat_uikit_context.dart                                 // 数据上下文,用于存储某些状态
│   ├── chat_uikit_conversation_extension.dart                  // 对会话列表进行处理的类,用于预处理某些属性
│   ├── chat_uikit_file_size_tool.dart                          // 用于计算显示的文件大小的工具
│   ├── chat_uikit_helper.dart                                  // 计算边框半径的内部类
│   ├── chat_uikit_highlight_tool.dart                          // 计算组件高亮值的工具类
│   ├── chat_uikit_image_loader.dart                            // 图片加载工具类
│   ├── chat_uikit_message_extension.dart                       // 消息处理类,用于预处理某些属性
│   ├── chat_uikit_time_tool.dart                               // 默认时间格式类
│   ├── chat_uikit_url_helper.dart                              // URL 预览工具类
│   └── safe_disposed.dart                                      // ChangeNotifier 的内部处理类
├── ui                                                          // UI 组件
│   ├── components                                              // 组件
│   ├── controllers                                             // 视图/小部件控制器
│   ├── custom                                                  // UI 自定义
│   ├── models                                                  // 模型
│   ├── route                                                   // UIKit 中的路由组件
│   ├── views                                                   // 视图
│   └── widgets                                                 // 小部件
└── universal                                                   // 内部类

快速开始

  1. 创建项目。
flutter create uikit_quick_start --platforms=android,ios
  1. 添加依赖。
cd uikit_quick_start
flutter pub add em_chat_uikit
flutter pub get
  1. 添加权限。
  • iOS: 在 <project root>/ios/Runner/Info.plist 中添加权限。
NSPhotoLibraryUsageDescription
NSCameraUsageDescription
NSMicrophoneUsageDescription
  1. 初始化 UIKit。
import 'package:em_chat_uikit/chat_uikit.dart';

void main() {
  ChatUIKit.instance
      .init(options: Options(appKey: '<!--Your AppKey-->', autoLogin: false))
      .then((value) {
    runApp(const MyApp());
  });
}
  1. 登录 UIKit。
Future<void> login() async {
  try {
    await ChatUIKit.instance.loginWithToken(
        userId: '<!--user id-->', token: '<!--user token-->');
  } catch (e) {
    debugPrint('login error: $e');
  }
}
  1. 创建聊天页面。
class ChatPage extends StatefulWidget {
  const ChatPage({required this.chatterId, super.key});
  final String chatterId;
  @override
  State<ChatPage> createState() => _ChatPageState();
}

class _ChatPageState extends State<ChatPage> {
  @override
  Widget build(BuildContext context) {
    return MessagesView(
      profile: ChatUIKitProfile.contact(
        id: widget.chatterId,
      ),
    );
  }
}

完整的代码:

import 'package:flutter/material.dart';
import 'package:em_chat_uikit/chat_uikit.dart';

const String appKey = '';
const String userId = '';
const String token = '';

const String chatterId = '';

void main() {
  ChatUIKit.instance
      .init(options: Options(appKey: appKey, autoLogin: false))
      .then((value) {
    runApp(MyApp());
  });
}

class MyApp extends StatelessWidget {
  MyApp({super.key});
  final ChatUIKitLocalizations _localization = ChatUIKitLocalizations();
  // 这个小部件是你的应用的根节点。
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      supportedLocales: _localization.supportedLocales,
      localizationsDelegates: _localization.localizationsDelegates,
      localeResolutionCallback: _localization.localeResolutionCallback,
      locale: _localization.currentLocale,
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ElevatedButton(
              onPressed: login,
              child: const Text('登录'),
            ),
            ElevatedButton(
              onPressed: chat,
              child: const Text('聊天'),
            ),
          ],
        ),
      ),
    );
  }

  Future<void> login() async {
    try {
      await ChatUIKit.instance.loginWithToken(
        userId: userId,
        token: token,
      );
    } catch (e) {
      debugPrint('登录错误: $e');
    }
  }

  void chat() {
    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) => const ChatPage(
          chatterId: chatterId,
        ),
      ),
    );
  }
}

class ChatPage extends StatefulWidget {
  const ChatPage({required this.chatterId, super.key});
  final String chatterId;
  @override
  State<ChatPage> createState() => _ChatPageState();
}

class _ChatPageState extends State<ChatPage> {
  @override
  Widget build(BuildContext context) {
    return MessagesView(
      profile: ChatUIKitProfile.contact(
        id: widget.chatterId,
      ),
    );
  }
}

高级使用

提供者(Provider)

提供者是一个数据提供器。如果需要显示用户数据或群组数据,UIKit 会通过提供者请求数据,并且你需要将数据返回给提供者。一旦 UIKit 获取到你的数据,它会刷新 UI 来展示你的数据。以下是一个提供者的例子(example/lib/tool/user_provider_widget.dart)。

示例
class UserProviderWidget extends StatefulWidget {
  const UserProviderWidget({required this.child, super.key});

  final Widget child;

  @override
  State<UserProviderWidget> createState() => _UserProviderWidgetState();
}

class _UserProviderWidgetState extends State<UserProviderWidget> with GroupObserver {
  @override
  void initState() {
    super.initState();
    ChatUIKit.instance.addObserver(this);
    // 打开数据库
    UserDataStore().init(onOpened: onOpened);
    // 设置提供者处理器
    ChatUIKitProvider.instance.profilesHandler = onProfilesRequest;
  }

  @override
  void dispose() {
    ChatUIKit.instance.removeObserver(this);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return widget.child;
  }

  void onOpened() async {
    // 1. 将所有存储的数据填充到 UIKit。
    await addAllUserInfoToProvider();
    // 2. 加载群组信息,检查是否已填充到 UIKit。如果没有,则从服务器获取数据并填充到 UIKit。
    await loadGroupInfos();
    // 3. 加载用户信息,检查是否已填充到 UIKit。如果没有,则从服务器获取数据并填充到 UIKit。
    await loadUserInfos();
    // 4. 获取当前用户信息,然后填充到 UIKit。
    await fetchCurrentUserInfo();
  }

  Future<void> fetchCurrentUserInfo() async {
    try {
      // 不要从数据库检索自己的用户数据,总是从服务器获取最新数据。
      Map<String, UserInfo> map = await ChatUIKit.instance.fetchUserInfoByIds([ChatUIKit.instance.currentUserId!]);
      ChatUIKitProfile profile = ChatUIKitProfile.contact(
        id: map.values.first.userId,
        nickname: map.values.first.nickName,
        avatarUrl: map.values.first.avatarUrl,
      );
      UserDataStore().saveUserData(profile);
      ChatUIKitProvider.instance.addProfiles([profile]);
    } catch (e) {
      debugPrint('获取当前用户信息错误: $e');
    }
  }

  // 当 UIKit 需要显示用户信息且缓存不存在时;它需要根据用户属性获取并存储信息到数据库。
  List<ChatUIKitProfile>? onProfilesRequest(List<ChatUIKitProfile> profiles) {
    List<String> userIds = profiles
        .where((e) => e.type == ChatUIKitProfileType.contact)
        .map((e) => e.id)
        .toList();
    if (userIds.isNotEmpty) {
      fetchUserInfos(userIds);
    }

    List<String> groupIds = profiles
        .where((e) => e.type == ChatUIKitProfileType.group)
        .map((e) => e.id)
        .toList();
    updateGroupsProfile(groupIds);
    return profiles;
  }

  // 当你创建了一个群组,需要将群组信息填充到 UIKit。
  @override
  void onGroupCreatedByMyself(Group group) async {
    ChatUIKitProfile profile =
        ChatUIKitProfile.group(id: group.groupId, groupName: group.name);

    ChatUIKitProvider.instance.addProfiles([profile]);
    // 保存到数据库
    UserDataStore().saveUserData(profile);
  }

  // 当你自己更改了群组名称,需要更新 UIKit 中的群组信息。
  @override
  void onGroupNameChangedByMeSelf(Group group) {
    ChatUIKitProfile? profile =
        ChatUIKitProvider.instance.getProfileById(group.groupId);

    profile = profile?.copyWith(name: group.name) ??
        ChatUIKitProfile.group(
          id: group.groupId,
          groupName: group.name,
        );

    ChatUIKitProvider.instance.addProfiles([profile]);
    // 保存到数据库
    UserDataStore().saveUserData(profile);
  }

  // 将所有存储的数据填充到 UIKit。
  Future<void> addAllUserInfoToProvider() async {
    List<ChatUIKitProfile> list = await UserDataStore().loadAllProfiles();
    ChatUIKitProvider.instance.addProfiles(list);
  }

  // 加载群组信息,检查是否已填充到 UIKit。如果没有,则从服务器获取数据并填充到 UIKit。
  Future<void> loadGroupInfos() async {
    List<Group> groups = await ChatUIKit.instance.getJoinedGroups();
    List<ChatUIKitProfile> profiles = groups
        .map((e) => ChatUIKitProfile.group(id: e.groupId, groupName: e.name))
        .toList();

    if (profiles.isNotEmpty) {
      UserDataStore().saveUserDatas(profiles);
      ChatUIKitProvider.instance.addProfiles(profiles);
    }
  }

  Future<void> updateGroupsProfile(List<String> groupIds) async {
    List<ChatUIKitProfile> list = [];
    for (var groupId in groupIds) {
      try {
        Group group = await ChatUIKit.instance.fetchGroupInfo(groupId: groupId);
        ChatUIKitProfile profile = ChatUIKitProfile.group(
          id: group.groupId,
          groupName: group.name,
          avatarUrl: group.extension,
        );
        list.add(profile);
      } on ChatError catch (e) {
        if (e.code == 600) {
          // 600 表示群组不存在,无法获取数据,提供默认数据。
          ChatUIKitProfile profile = ChatUIKitProfile.group(id: groupId);
          list.add(profile);
        }
        debugPrint('加载群组信息错误: $e');
      }
    }
    UserDataStore().saveUserDatas(list);
    ChatUIKitProvider.instance.addProfiles(list);
  }

  // 加载用户信息,检查是否已填充到 UIKit。如果没有,则从服务器获取数据并填充到 UIKit。
  Future<void> loadUserInfos() async {
    try {
      Map<String, ChatUIKitProfile> map =
          ChatUIKitProvider.instance.profilesCache;
      List<Contact> contacts = await ChatUIKit.instance.getAllContacts();
      contacts.removeWhere((element) => map.keys.contains(element.userId));
      if (contacts.isNotEmpty) {
        List<String> userIds = contacts.map((e) => e.userId).toList();
        fetchUserInfos(userIds);
      }
    } catch (e) {
      debugPrint('加载用户信息错误: $e');
    }
  }

  void fetchUserInfos(List<String> userIds) async {
    try {
      Map<String, UserInfo> map =
          await ChatUIKit.instance.fetchUserInfoByIds(userIds);
      List<ChatUIKitProfile> list = map.values
          .map((e) => ChatUIKitProfile.contact(
              id: e.userId, nickname: e.nickName, avatarUrl: e.avatarUrl))
          .toList();

      if (list.isNotEmpty) {
        UserDataStore().saveUserDatas(list);
        ChatUIKitProvider.instance.addProfiles(list);
      }
    } catch (e) {
      debugPrint('获取用户信息错误: $e');
    }
  }
}
使用

在使用 ChatUIKitProvider 之前,需要设置 profilesHandler。之后,当需要显示相关信息时,UIKit 会通过处理器返回一个默认的 ChatUIKitProfile 对象,你需要返回一个 ChatUIKitProfile 对象。建议首先返回一个占位符的 ChatUIKitProfile 对象。当你从服务器或数据库获取到正确的 ChatUIKitProfile 对象后,通过 ChatUIKitProvider.instance.addProfiles(list) 方法将其传递给 UIKit。接收该对象后,UIKit 会刷新 UI 并进行缓存以供后续显示。以下是使用 ChatUIKitProvider 的步骤:

  1. 在应用启动和登录时设置 profilesHandler
ChatUIKitProvider.instance.profilesHandler = onProfilesRequest;
  1. 通过 ChatUIKitProvider 将用户数据和群组数据设置到 UIKit。
List<ChatUIKitProfile> list = await UserDataStore().loadAllProfiles();
ChatUIKitProvider.instance.addProfiles(list);
  1. 当执行 profilesHandler 时,你可以首先返回占位符的 ChatUIKitProfile 对象,从服务器获取数据,然后将其保存到数据库并传递给 UIKit。
List<ChatUIKitProfile>? onProfilesRequest(List<ChatUIKitProfile> profiles) {
  List<String> userIds = profiles
      .where((e) => e.type == ChatUIKitProfileType.contact)
      .map((e) => e.id)
      .toList();
  if (userIds.isNotEmpty) {
    fetchUserInfos(userIds);
  }

  List<String> groupIds = profiles
      .where((e) => e.type == ChatUIKitProfileType.group)
      .map((e) => e.id)
      .toList();
  updateGroupsProfile(groupIds);
  return profiles;
}

配置项

ChatUIKit 允许通过 ChatUIKitSettings 进行样式自定义。

import 'chat_uikit.dart';

import 'package:flutter/material.dart';

enum CornerRadius { extraSmall, small, medium, large }

class ChatUIKitSettings {
  /// 指定 UIKit 中头像的圆角半径。
  static CornerRadius avatarRadius = CornerRadius.medium;

  /// 指定搜索框的圆角半径。
  static CornerRadius searchBarRadius = CornerRadius.small;

  /// 指定输入框的圆角半径。
  static CornerRadius inputBarRadius = CornerRadius.medium;

  /// 默认头像占位符图像。
  static ImageProvider? avatarPlaceholder;

  /// 对话框的圆角类型。
  static ChatUIKitDialogRectangleType dialogRectangleType =
      ChatUIKitDialogRectangleType.filletCorner;

  /// 消息气泡的默认显示样式。
  static ChatUIKitMessageListViewBubbleStyle messageBubbleStyle =
      ChatUIKitMessageListViewBubbleStyle.arrow;

  /// 是否在会话列表中显示头像。
  static bool showConversationListAvatar = true;

  /// 是否在会话列表中显示未读消息数量。
  static bool showConversationListUnreadCount = true;

  // 会话列表中显示的静音图标。
  static ImageProvider? conversationListMuteImage;

  /// 消息长按菜单。
  static List<ChatUIKitActionType> msgItemLongPressActions = [
    ChatUIKitActionType.reaction,
    ChatUIKitActionType.copy, // 仅文本消息。
    ChatUIKitActionType.forward,
    ChatUIKitActionType.thread, // 仅群消息。
    ChatUIKitActionType.reply,
    ChatUIKitActionType.recall,
    ChatUIKitActionType.edit, // 仅文本消息。
    ChatUIKitActionType.multiSelect,
    ChatUIKitActionType.pinMessage,
    ChatUIKitActionType.translate, // 仅文本消息。
    ChatUIKitActionType.report,
    ChatUIKitActionType.delete,
  ];

  /// 是否启用一对一聊天消息的输入状态功能。
  static bool enableTypingIndicator = true;

  /// 是否启用线程功能。
  static bool enableMessageThread = true;

  /// 是否启用消息翻译功能。
  static bool enableMessageTranslation = true;

  /// 消息翻译的目标语言。
  static String translateTargetLanguage = 'zh-Hans';

  /// 是否启用消息反应功能。
  static bool enableMessageReaction = true;

  /// 消息反应表情在底部表单标题中。这些表情需要包含在表情列表 [ChatUIKitEmojiData.emojiList] 中。
  static List<String> favoriteReaction = [   
    '\u{1F44D}',
    '\u{2764}',
    '\u{1F609}',
    '\u{1F928}',
    '\u{1F62D}',
    '\u{1F389}',
  ];

  /// 默认消息 URL 的正则表达式。
  static RegExp defaultUrlRegExp = RegExp(
    r'(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-?=%.]+',
    caseSensitive: false,
  );

  /// 是否启用消息固定功能。
  static bool enablePinMsg = true;

  /// 是否启用消息引用功能。
  static bool enableMessageReply = true;

  /// 是否启用消息撤回功能。
  static bool enableMessageRecall = true;

  /// 消息撤回的时间限制,单位为秒。
  static int recallExpandTime = 120;

  /// 是否启用消息编辑功能。
  static bool enableMessageEdit = true;

  /// 是否启用消息举报功能。
  static bool enableMessageReport = true;

  /// 消息举报标签,可以自定义。举报原因应该写入本地化文件中,本地化文件中的原因键应与标签一致。例如,[ChatUIKitLocal.reportTarget1]
  static List<String> reportMessageTags = [
    'tag1',
    'tag2',
    'tag3',
    'tag4',
    'tag5',
    'tag6',
    'tag7',
    'tag8',
    'tag9',
  ];

  /// 是否启用消息多选功能。
  static bool enableMessageMultiSelect = true;

  /// 是否启用消息转发功能。
  static bool enableMessageForward = true;

  /// 联系人 `showName` 的字母排序。如果有中文字符,可以使用 [ChatUIKitAlphabetSortHelper] 重新定义首字母。
  static String sortAlphabetical = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ#';
}

国际化

UIKit 提供了国际化功能。在集成 UIKit 时,你需要在 MaterialApp 中设置国际化信息。

final ChatUIKitLocalizations _localization = ChatUIKitLocalizations();

...

@override
Widget build(BuildContext context) {
  return MaterialApp(
    supportedLocales: _localization.supportedLocales,
    localizationsDelegates: _localization.localizationsDelegates,
    localeResolutionCallback: _localization.localeResolutionCallback,
    locale: _localization.currentLocale,
    ...
  );
}

要添加支持的语言,你可以先使用 ChatUIKitLocalizations.addLocales,然后调用 ChatUIKitLocalizations.resetLocales

以下是如何添加法语的例子。

_localization.addLocales(locales: const [
  ChatLocal('fr', {
    ChatUIKitLocal.conversationsViewSearchHint: 'Recherche',
  })
]);

_localization.resetLocales();

主题

UIKit 自带两个主题:浅色和深色,默认为浅色主题。设置主题时,建议添加 ChatUIKitTheme 组件作为 UIKit 的根节点。

return MaterialApp(
  title: 'Flutter Demo',
  theme: ThemeData(
    colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
    useMaterial3: true,
  ),
  builder: (context, child) {
    /// 添加主题支持
    return ChatUIKitTheme(
      font: ChatUIKitFont(),
      color: ChatUIKitColor.light(), // ChatUIKitColor.dark()
      child: child!,
    );
  },
  home: const MyHomePage(title: 'Flutter Demo Home Page'),
);

ChatUIKitColor 可以通过调整 hue 来自定义。例如,在浅色模式下调整 hue 值。

return MaterialApp(
  title: 'Flutter Demo',
  theme: ThemeData(
    colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
    useMaterial3: true,
  ),
  builder: (context, child) {
    return ChatUIKitTheme(
      color: ChatUIKitColor.light(
        primaryHue: 203,
        secondaryHue: 155,
        errorHue: 350,
        neutralHue: 203,
        neutralSpecialHue: 220,
      ),
      child: child!,
    );
  },
  home: const MyHomePage(title: 'Flutter Demo Home Page'),
); 

ChatUIKitFont 允许你设置字体大小。例如,你可以通过 ChatUIKitFont.fontSize(fontSize: ChatUIKitFontSize.normal) 传递不同的 ChatUIKitFontSize 类型来改变 UIKit 的字体大小。

路由拦截和定制

UIKit 使用 pushNamed 实现重定向,通过目标重定向页面的 ChatUIKitViewArguments 对象传递。你可以拦截 onGenerateRoute(RouteSettings settings) 并解析 settings.name 来获取目标重定向页面。然后,你可以重置 ChatUIKitViewArguments 参数以实现重定向拦截和页面定制。目标重定向页面的名称在 chat_uikit_route_names.dart 中指定。

有关路由拦截的详细信息,可以参考 example/lib/custom/chat_route_filter.dart

事件拦截和错误处理

当 UIKit 开始调用聊天 SDK 时,ChatSDKEventsObserver.onChatSDKEventBegin 会被触发。当调用结束时,ChatSDKEventsObserver.onChatSDKEventEnd 会被触发。如果发生错误,会报告 ChatError

class SDKEventHandlerPage extends StatefulWidget {
  const SDKEventHandlerPage({super.key});

  @override
  State<SDKEventHandlerPage> createState() => _SDKEventHandlerPageState();
}

class _SDKEventHandlerPageState extends State<SDKEventHandlerPage> with ChatSDKEventsObserver {
  @override
  void initState() {
    ChatUIKit.instance.addObserver(this);
    super.initState();
  }

  @override
  void dispose() {
    ChatUIKit.instance.removeObserver(this);
    super.dispose();
  }

  /// 当调用 SDK 方法开始时,可以根据不同事件显示不同的提示窗口。
  @override
  void onChatSDKEventBegin(ChatSDKEvent event) {}

  /// 当调用 SDK 方法结束时,此时可以结束提示窗口的显示。如果发生错误,可以显示相应的提示消息。
  @override
  void onChatSDKEventEnd(ChatSDKEvent event, ChatError? error) {}

  ...
}

更多详细信息,可以参考 example/lib/tool/toast_page.dart

对于其他非聊天 SDK 的事件,ChatUIKitEventsObservers.onChatUIKitEventsReceived 会被触发。

class UIKitEventHandlePage extends StatefulWidget {
  const UIKitEventHandlePage({super.key});

  @override
  State<UIKitEventHandlePage> createState() => _UIKitEventHandlePageState();
}

class _UIKitEventHandlePageState extends State<UIKitEventHandlePage> with ChatUIKitEventsObservers {
  @override
  void initState() {
    ChatUIKit.instance.addObserver(this);
    super.initState();
  }

  @override
  void dispose() {
    ChatUIKit.instance.removeObserver(this);
    super.dispose();
  }

  /// 此方法用于将事件从 ChatUIKit 传递给开发者。
  @override
  void onChatUIKitEventsReceived(ChatUIKitEvent event) {}

  ...
}

更多详细信息,见 example/lib/tool/toast_page.dart

连接状态变化和登录令牌过期回调

当连接状态或登录状态发生变化时,ChatUIKit.instance.connectHandler 中的相应事件会被触发。

ChatUIKit.instance.connectHandler(
  onUserAuthenticationFailed: () {},
  onUserDidChangePassword: () {},
  onUserDidForbidByServer: () {},
  onUserDidLoginFromOtherDevice: (info) {},
  onUserDidLoginTooManyDevice: () {},
  onUserDidRemoveFromServer: () {},
  onUserKickedByOtherDevice: () {},
  onConnected: () {},
  onDisconnected: () {},
  onTokenWillExpire: () {},
  onTokenDidExpire: () {},
  onAppActiveNumberReachLimit: () {},
);

更多详细信息,可以参考 example/lib/tool/token_status_handler_widget.dart

消息时间格式化

UIKit 默认以某种格式显示时间。你可以调用 ChatUIKitTimeFormatter 来改变时间的显示方式。

ChatUIKitTimeFormatter.instance.formatterHandler = (context, type, time) {
  return 'formatter time'; // 返回格式化的时间,如 12:00 PM
};

联系人字母顺序修正

例如,如果联系人的名字包含除英文字母以外的其他字符,你可以使用 ChatUIKitAlphabetSortHelper 来按字母顺序排序联系人。

ChatUIKitAlphabetSortHelper.instance.sortHandler = (String? groupId, String userId, String showName) {
  // 返回 showName 的第一个字母用于排序,特别是对中文字符有用
  return PinyinHelper.getFirstWordPinyin(showName);
};

更多关于Flutter即时通讯UI组件插件em_chat_uikit的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter即时通讯UI组件插件em_chat_uikit的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


em_chat_uikit 是一个用于 Flutter 的即时通讯 UI 组件插件,由环信(Easemob)提供。它基于环信的即时通讯 SDK,简化了开发者在 Flutter 应用中集成聊天功能的过程。以下是如何使用 em_chat_uikit 插件的步骤:

1. 添加依赖

首先,在你的 pubspec.yaml 文件中添加 em_chat_uikit 依赖:

dependencies:
  flutter:
    sdk: flutter
  em_chat_uikit: ^1.0.0  # 请检查最新版本

然后运行 flutter pub get 来安装依赖。

2. 初始化环信 SDK

在使用 em_chat_uikit 之前,你需要初始化环信 SDK。通常在你的 main.dart 文件中进行初始化:

import 'package:em_chat_uikit/em_chat_uikit.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // 初始化环信 SDK
  await EMChatUIKit.instance.init(
    appKey: 'your_app_key',  // 替换为你的环信 App Key
    apnsCertName: 'your_apns_certificate_name',  // iOS 推送证书名称,可选
  );
  
  runApp(MyApp());
}

3. 用户登录

在用户登录界面,使用 EMChatUIKit 提供的方法进行用户登录:

void login(String username, String password) async {
  try {
    await EMChatUIKit.instance.login(username, password);
    // 登录成功后,导航到主界面
    Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => HomeScreen()));
  } catch (e) {
    // 处理登录失败
    print('Login failed: $e');
  }
}

4. 使用聊天 UI 组件

em_chat_uikit 提供了多个预构建的聊天 UI 组件,你可以直接在应用中使用。

聊天列表

显示用户的好友列表或群组列表:

import 'package:em_chat_uikit/em_chat_uikit.dart';

class ChatListScreen extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Chats'),
      ),
      body: EMChatListView(),
    );
  }
}

聊天界面

显示与特定用户的聊天界面:

import 'package:em_chat_uikit/em_chat_uikit.dart';

class ChatScreen extends StatelessWidget {
  final String userId;

  ChatScreen(this.userId);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(userId),
      ),
      body: EMChatView(
        conversationId: userId,
        conversationType: EMConversationType.Chat,
      ),
    );
  }
}

5. 处理消息事件

你可以监听消息事件来处理新消息、消息发送状态等。

import 'package:em_chat_uikit/em_chat_uikit.dart';

class ChatScreen extends StatefulWidget {
  final String userId;

  ChatScreen(this.userId);

  [@override](/user/override)
  _ChatScreenState createState() => _ChatScreenState();
}

class _ChatScreenState extends State<ChatScreen> {
  [@override](/user/override)
  void initState() {
    super.initState();
    EMChatUIKit.instance.addMessageListener(onMessageReceived);
  }

  [@override](/user/override)
  void dispose() {
    EMChatUIKit.instance.removeMessageListener(onMessageReceived);
    super.dispose();
  }

  void onMessageReceived(EMMessage message) {
    // 处理新消息
    print('New message received: ${message.body}');
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.userId),
      ),
      body: EMChatView(
        conversationId: widget.userId,
        conversationType: EMConversationType.Chat,
      ),
    );
  }
}
回到顶部