chat_uikit for Flutter

This guide gives a comprehensive overview into chat_uikit. The new chat_uikit is intended to provide developers with an efficient, plug-and-play, and highly customizable UI component library, helping you build complete and elegant IM applications that can easily satisfy most instant messaging scenarios. Please download the demo to try it out.

chat_uikit Guide

Introduction

This guide provides an overview and usage examples of the chat_uikit framework in Flutter development, and presents various components and functions of this UIKit, giving developers a good understanding of how chat_uikit works and how to use it efficiently.

Table of contents

Development Environment

environment:
  sdk: '>=3.0.0 <4.0.0'
  flutter: ">=3.19.0"
  • ios 12+
  • android minSDKVersion 24

Installation

flutter pub add em_chat_uikit

Structure

.
├── chat_uikit.dart                                             // library
├── chat_uikit_alphabet_sort_helper.dart                        // Tool to correct the alphabetical order of contacts
├── chat_uikit_defines.dart                                     // UIKit handler definition class
├── chat_uikit_emoji_data.dart                                  // Message emoji data class
├── chat_uikit_localizations.dart                               // Internationalization tool class
├── chat_uikit_service                                          // Secondary wrapping for the chat SDK wrapper class, used to adapt incompliant functions in the wrapping class to UIKit functions.
├── chat_uikit_settings.dart                                    // Class to set functions, used to turn on or off or configure certain functions
├── chat_uikit_time_formatter.dart                              // Tool to set the displayed time format
├── provider                                                    // User attribute tool
│   ├── chat_uikit_profile.dart                                 // User attribute object, including the user's avatar, nickname, and remarks.
│   └── chat_uikit_provider.dart                                // User attribute provider, used to provide user attribute data in the UIKit. 
├── sdk_service                                                 // Wrapping for the chat SDK, used to wrap APIs in the chat SDK that are available to developers. The UIKit interacts with the wrapping class, instead of calling APIs in the chat SDK.
├── tools                                                       // Internal tool class
│   ├── chat_uikit_context.dart                                 // Data context to store certain states.
│   ├── chat_uikit_conversation_extension.dart                  // Processed class of the conversation list to pre-process certain properties.
│   ├── chat_uikit_file_size_tool.dart                          // Tool to calculate the displayed file size.
│   ├── chat_uikit_helper.dart                                  // Internal class to calculate the border radius
│   ├── chat_uikit_highlight_tool.dart                          // Tool class to calculate the component highlight value
│   ├── chat_uikit_image_loader.dart                            // Image loading tool class
│   ├── chat_uikit_message_extension.dart                       // Message processing class that pre-processes certain properties
│   ├── chat_uikit_time_tool.dart                               // Default time format class
│   ├── chat_uikit_url_helper.dart                              // Tool class for URL preview
│   └── safe_disposed.dart                                      // Internal processing class for ChangeNotifier
├── ui                                                          // UI components
│   ├── components                                              // Components
│   ├── controllers                                             // View/widget controllers
│   ├── custom                                                  // UI customization
│   ├── models                                                  // models
│   ├── route                                                   // Route component in the UIKit
│   ├── views                                                   // Views
│   └── widgets                                                 // Widgets
└── universal                                                   // Internal class

Quick Start

  1. Create a project.
flutter create uikit_quick_start --platforms=android,ios
  1. Add dependencies.
cd uikit_quick_start
flutter pub add em_chat_uikit
flutter pub get
  1. Add permissions.
  • iOS: Add permissions in <project root>/ios/Runner/Info.plist.
NSPhotoLibraryUsageDescription
NSCameraUsageDescription
NSMicrophoneUsageDescription
  1. Initialize the 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. Log in to the UIKit.
Future<void> login() async {
  try {
    await ChatUIKit.instance.loginWithToken(
        userId: '<!--user id-->',
        token: '<!--user token-->');
  } catch (e) {
    debugPrint('login error: $e');
  }
}
  1. Create the chat page.
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,
      ),
    );
  }
}

Complete code:

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();
  // This widget is the root of your application.
  @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('Login'),
            ),
            ElevatedButton(
              onPressed: chat,
              child: const Text('Chat'),
            ),
          ],
        ),
      ),
    );
  }

  Future<void> login() async {
    try {
      await ChatUIKit.instance.loginWithToken(
        userId: userId,
        token: token,
      );
    } catch (e) {
      debugPrint('login error: $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,
      ),
    );
  }
}

Advanced usage

Provider

Provider is a data provider. If user data or group data needs to be displayed, the UIKit will request data via the Provider and you need to return data to the Provider. The UIKit, once getting your data, will refresh the UI to show your data. Following is an example of Provider (example/lib/tool/user_provider_widget.dart).

Example


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);
    // Open DB
    UserDataStore().init(onOpened: onOpened);
    // Set Provider Handler
    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. Fill all stored data into uikit.
    await addAllUserInfoToProvider();
    // 2. Load group information, and check if it has been filled into uikit. If not, fetch data from the server and then fill it into uikit.
    await loadGroupInfos();
    // 2. Load user information, and check if it has been filled into uikit. If not, fetch data from the server and then fill it into uikit.
    await loadUserInfos();
    // 3. Fetch current user information, then fill it into uikit.
    await fetchCurrentUserInfo();
  }

  Future<void> fetchCurrentUserInfo() async {
    try {
      // Do not retrieve your own user data from the db, always fetch the latest data from the server. 
      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('fetchCurrentUserInfo error: $e');
    }
  }

  // This method is called when uikit needs to display user information and the cache does not exist;
  // it requires fetching and storing the information in the db based on user attributes.
  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;
  }

  // When a group is created by yourself, it is necessary to fill the group information into uikit. 
  @override
  void onGroupCreatedByMyself(Group group) async {
    ChatUIKitProfile profile =
        ChatUIKitProfile.group(id: group.groupId, groupName: group.name);

    ChatUIKitProvider.instance.addProfiles([profile]);
    // save to db
    UserDataStore().saveUserData(profile);
  }

  // When the group name is changed by yourself, it is necessary to update the group information in 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]);
    // save to db
    UserDataStore().saveUserData(profile);
  }

  // Fill all stored data into uikit.
  Future<void> addAllUserInfoToProvider() async {
    List<ChatUIKitProfile> list = await UserDataStore().loadAllProfiles();
    ChatUIKitProvider.instance.addProfiles(list);
  }

  // Load group information, and check if it has been filled into uikit. If not, fetch data from the server and then fill it into 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 indicates the group does not exist, unable to fetch data, providing default data.
          ChatUIKitProfile profile = ChatUIKitProfile.group(id: groupId);
          list.add(profile);
        }
        debugPrint('loadGroupInfo error: $e');
      }
    }
    UserDataStore().saveUserDatas(list);
    ChatUIKitProvider.instance.addProfiles(list);
  }

  // Load user information, and check if it has been filled into uikit. If not, fetch data from the server and then fill it into 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('loadUserInfos error: $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('fetchUserInfos error: $e');
    }
  }
}

Usage

A hanlder profilesHandler is required before the use of ChatUIKitProvider. After that, when related information needs to be displayed, UIKit will return you a default ChatUIKitProfile object via the handler and you need to return a ChatUIKitProfile object. In this case, you are advised to return a ChatUIKitProfile object for the placeholding purpose. When you have obtained the correct ChatUIKitProfile object from your server or database, pass it to the UIKit using the ChatUIKitProvider.instance.addProfiles(list) method. Receiving the object, the UIKit refreshes the UI and caches it for subsequent displaying. Take the following steps to use ChatUIKitProvider:

  1. Set profilesHandler upon the app start and login.
    ChatUIKitProvider.instance.profilesHandler = onProfilesRequest;
  1. Set the user data and group data in the database to the UIKit via ChatUIKitProvider.
    List<ChatUIKitProfile> list = await UserDataStore().loadAllProfiles();
    ChatUIKitProvider.instance.addProfiles(list);
  1. When profilesHandler is executed, you can first return ChatUIKitProfile for the placeholding purpose, get data from the server, and then save the data to the database and pass it to the 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;
  }

Configuration items

ChatUIKit allows easy style customization via ChatUIKitSettings.

import 'chat_uikit.dart';

import 'package:flutter/material.dart';

enum CornerRadius { extraSmall, small, medium, large }

class ChatUIKitSettings {
  /// Specifies the corner radius for the avatars in the uikit.
  static CornerRadius avatarRadius = CornerRadius.medium;

  /// Specifies the corner radius of the search box.
  static CornerRadius searchBarRadius = CornerRadius.small;

  /// Specifies the corner radius of the input box.
  static CornerRadius inputBarRadius = CornerRadius.medium;

  /// Default avatar placeholder image.
  static ImageProvider? avatarPlaceholder;

  /// The corner radius for the dialog.
  static ChatUIKitDialogRectangleType dialogRectangleType =
      ChatUIKitDialogRectangleType.filletCorner;

  /// Default display style of message bubbles.
  static ChatUIKitMessageListViewBubbleStyle messageBubbleStyle =
      ChatUIKitMessageListViewBubbleStyle.arrow;

  /// Whether to show avatars in the conversation list.
  static bool showConversationListAvatar = true;

  /// Whether to show unread message count in the conversation list.
  static bool showConversationListUnreadCount = true;

  // Mute icon displayed in the conversation list.
  static ImageProvider? conversationListMuteImage;

  /// Message long press menu.
  static List<ChatUIKitActionType> msgItemLongPressActions = [
    ChatUIKitActionType.reaction,
    ChatUIKitActionType.copy, // only text message.
    ChatUIKitActionType.forward,
    ChatUIKitActionType.thread, // only group message.
    ChatUIKitActionType.reply,
    ChatUIKitActionType.recall,
    ChatUIKitActionType.edit, // only text message.
    ChatUIKitActionType.multiSelect,
    ChatUIKitActionType.pinMessage,
    ChatUIKitActionType.translate, // only text message.
    ChatUIKitActionType.report,
    ChatUIKitActionType.delete,
  ];

  /// Whether to enable the functionality of input status for one-on-one chat messages.
  static bool enableTypingIndicator = true;

  /// Whether to enable the thread feature.
  static bool enableMessageThread = true;

  /// Whether to enable message translation feature.
  static bool enableMessageTranslation = true;

  /// Message translation target language.
  static String translateTargetLanguage = 'zh-Hans';

  /// Whether to enable message reaction feature.
  static bool enableMessageReaction = true;

  /// Message reaction emojis in the bottom sheet title. These emojis need to be included in the emoji list [ChatUIKitEmojiData.emojiList].
  static List<String> favoriteReaction = [   
    '\u{1F44D}',
    '\u{2764}',
    '\u{1F609}',
    '\u{1F928}',
    '\u{1F62D}',
    '\u{1F389}',
  ];

  /// Regular expression for the default message URL  
  static RegExp defaultUrlRegExp = RegExp(
    r'(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-?=%.]+',
    caseSensitive: false,
  );

  /// Whether to enable the message pinning feature
  static bool enablePinMsg = true;

  /// Whether to enable message quoting feature
  static bool enableMessageReply = true;

  /// Whether to enable message recall feature
  static bool enableMessageRecall = true;

  /// Time limit for message recall, in seconds
  static int recallExpandTime = 120;

  /// Whether to enable message editing feature
  static bool enableMessageEdit = true;

  /// Whether to enable message reporting feature
  static bool enableMessageReport = true;

  /// Message report tags, can be customized. The reasons for reporting should be written in the localization file, and the key for the reason in the localization file should be consistent with the tag. For example, [ChatUIKitLocal.reportTarget1]
  static List<String> reportMessageTags = [
    'tag1',
    'tag2',
    'tag3',
    'tag4',
    'tag5',
    'tag6',
    'tag7',
    'tag8',
    'tag9',
  ];

  /// Whether to enable message multi-selection feature  
  static bool enableMessageMultiSelect = true;

  /// Whether to enable message forwarding feature
  static bool enableMessageForward = true;

  /// Alphabetical order of `showName` of contacts. If there are Chinese characters, you can redefine the initials using [ChatUIKitAlphabetSortHelper].
  static String sortAlphabetical = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ#';
}

Internationalization

UIKit provides the internationalization function. You need to set internationalization information in MaterialApp when integrating the UIKit.

final ChatUIKitLocalizations _localization = ChatUIKitLocalizations();

...

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

To add the supported languages, you can first use ChatUIKitLocalizations.addLocales, and then call ChatUIKitLocalizations.resetLocales.

Following is an example of adding French.

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

_localization.resetLocales();

Theme

UIKit comes with two themes: light and dark, with the former as the default. When setting the theme, you are advised to add ChatUIKitTheme component as root node of the UIKit.

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

ChatUIKitColor can be customized by adjusting hue. For example, adjust the hue value in light mode.

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 allows you to set the font size. For example, you can pass different types of ChatUIKitFontSize to ChatUIKitFont.fontSize(fontSize: ChatUIKitFontSize.normal) to change the font size of the UIKit.

Route interception and customization

The UIKit uses pushNamed to implement redirection, with the ChatUIKitViewArguments object of the target redirection page passed. You can intercept onGenerateRoute(RouteSettings settings) and parse settings.name to get the target page for redirection. Then, you can reset the ChatUIKitViewArguments parameter for redirection interception and page customization. The name of the target redirection page is specified in chat_uikit_route_names.dart.

For details on route interception, you can refer to example/lib/custom/chat_route_filter.dart.

Event interception and error handling

When the UIKit starts to call the chat SDK, ChatSDKEventsObserver.onChatSDKEventBegin is triggered. When the call ends, ChatSDKEventsObserver.onChatSDKEventEnd is triggered. If an error occurs, ChatError is reported.

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();
  }

  /// When the call to an SDK method starts, you can display different prompt windows based on different events.
  @override
  void onChatSDKEventBegin(ChatSDKEvent event) {}

  /// When the call to an SDK method call ends, you can end the prompt window display at this time. If there is an error, you can display the corresponding prompt message.
  @override
  void onChatSDKEventEnd(ChatSDKEvent event, ChatError? error) {}

  ...
}

For more information, you can refer to example/lib/tool/toast_page.dart.

For other events than those of the chat SDK, ChatUIKitEventsObservers.onChatUIKitEventsReceived is triggered.

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();
  }

  /// This method is used to pass events from ChatUIKit to developers.
  @override
  void onChatUIKitEventsReceived(ChatUIKitEvent event) {}

  ...
}

For more information, see example/lib/tool/toast_page.dart.

Connection status change and login token expiration callback

When the connection status or login status changes, the corresponding event in ChatUIKit.instance.connectHandler is triggered.

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

For more information, you can refer to example/lib/tool/token_status_handler_widget.dart.

Message time formatting

UIKit presents time in the default format. You can call ChatUIKitTimeFormatter to alter the way the time is formatted.

ChatUIKitTimeFormatter.instance.formatterHandler = (context, type, time) {
  return 'formatter time'; // return formatted time, e.g. 12:00 PM
};

Correction of the alphabetical order of contacts

For example, if a contact name contains other characters than English letters, you can use ChatUIKitAlphabetSortHelper to sort contacts in the alphabetical order.

ChatUIKitAlphabetSortHelper.instance.sortHandler = (String? groupId, String userId, String showName) {
  // Return the first letter of the showName for sorting, especially useful for sorting Chinese characters
  return PinyinHelper.getFirstWordPinyin(showName);
};

Design guide

For any questions about design guidelines and details, you can add comments to the Figma design draft and mention our designer Stevie Jiang.

Libraries

chat_uikit
chat_uikit_alphabet_sort_helper
chat_uikit_emoji_data
chat_uikit_localizations
chat_uikit_service/actions/chat_uikit_chat_actions
chat_uikit_service/actions/chat_uikit_contact_actions
chat_uikit_service/actions/chat_uikit_events_actions
chat_uikit_service/actions/chat_uikit_group_actions
chat_uikit_service/actions/chat_uikit_notification_actions
chat_uikit_service/chat_uikit_action_events
chat_uikit_service/chat_uikit_service
chat_uikit_service/observers/chat_uikit_chat_observers
chat_uikit_service/observers/chat_uikit_contact_observers
chat_uikit_service/observers/chat_uikit_events_observers
chat_uikit_service/observers/chat_uikit_group_observers
chat_uikit_service/observers/chat_uikit_multi_observers
chat_uikit_service/observers/chat_uikit_thread_observers
chat_uikit_settings
chat_uikit_time_formatter
provider/chat_uikit_profile
provider/chat_uikit_provider
sdk_service/actions/chat_actions
sdk_service/actions/contact_actions
sdk_service/actions/group_actions
sdk_service/actions/notification_actions
sdk_service/actions/presence_actions
sdk_service/actions/room_actions
sdk_service/actions/thread_actions
sdk_service/actions/user_info_actions
sdk_service/chat_sdk_define
sdk_service/chat_sdk_service
sdk_service/chat_sdk_service_defines
sdk_service/chat_uikit_insert_tools
sdk_service/observers/action_event_observer
sdk_service/observers/chat_observer
sdk_service/observers/connect_observer
sdk_service/observers/contact_observer
sdk_service/observers/group_observer
sdk_service/observers/message_observer
sdk_service/observers/multi_observer
sdk_service/observers/presence_observer
sdk_service/observers/room_observer
sdk_service/observers/thread_observer
sdk_service/wrappers/chat_wrapper
sdk_service/wrappers/connect_wrapper
sdk_service/wrappers/contact_wrapper
sdk_service/wrappers/group_wrapper
sdk_service/wrappers/message_wrapper
sdk_service/wrappers/multi_wrapper
sdk_service/wrappers/notification_wrapper
sdk_service/wrappers/presence_wrapper
sdk_service/wrappers/room_wrapper
sdk_service/wrappers/thread_wrapper
sdk_service/wrappers/user_info_wrapper
tools/chat_string_extension
tools/chat_uikit_context
tools/chat_uikit_conversation_extension
tools/chat_uikit_file_size_tool
tools/chat_uikit_helper
tools/chat_uikit_highlight_tool
tools/chat_uikit_image_loader
tools/chat_uikit_message_extension
tools/chat_uikit_time_tool
tools/chat_uikit_url_helper
tools/safe_disposed
ui/components/block_list_view
ui/components/contact_list_view
ui/components/conversation_list_view
ui/components/group_list_view
ui/components/group_member_list_view
ui/components/message_list_view
ui/components/new_requests_list_view
ui/components/pin_message_list_view
ui/components/thread_message_list_view
ui/controllers/block_list_view_controller
ui/controllers/chat_uikit_list_view_controller_base
ui/controllers/contact_list_view_controller
ui/controllers/conversation_list_view_controller
ui/controllers/group_list_view_controller
ui/controllers/group_member_list_view_controller
ui/controllers/messages_view_controller
ui/controllers/new_request_list_view_controller
ui/controllers/pin_message_list_view_controller
ui/controllers/thread_members_view_controller
ui/controllers/thread_messages_view_controller
ui/custom/custom_tab_indicator
ui/custom/share_user_data
ui/models/alphabetical_item_model
ui/models/chat_uikit_list_item_model_base
ui/models/contact_item_model
ui/models/conversation_item_model
ui/models/group_item_model
ui/models/message_model
ui/models/new_request_item_model
ui/models/pin_list_item_model
ui/models/quote_mode
ui/route/chat_uikit_route
ui/route/chat_uikit_route_names
ui/route/chat_uikit_view_observer
ui/route/view_arguments/change_info_view_arguments
ui/route/view_arguments/contact_details_view_arguments
ui/route/view_arguments/contacts_view_arguments
ui/route/view_arguments/conversations_view_arguments
ui/route/view_arguments/create_group_view_arguments
ui/route/view_arguments/current_user_info_view_arguments
ui/route/view_arguments/forward_message_select_view_arguments
ui/route/view_arguments/forward_messages_view_arguments
ui/route/view_arguments/group_add_members_view_arguments
ui/route/view_arguments/group_change_owner_view_arguments
ui/route/view_arguments/group_delete_members_view_arguments
ui/route/view_arguments/group_details_view_arguments
ui/route/view_arguments/group_members_view_arguments
ui/route/view_arguments/group_mention_view_arguments
ui/route/view_arguments/groups_view_arguments
ui/route/view_arguments/messages_view_arguments
ui/route/view_arguments/new_request_details_view_arguments
ui/route/view_arguments/new_requests_view_arguments
ui/route/view_arguments/report_message_view_arguments
ui/route/view_arguments/search_group_members_view_arguments
ui/route/view_arguments/search_history_view_arguments
ui/route/view_arguments/search_view_arguments
ui/route/view_arguments/select_contact_view_arguments
ui/route/view_arguments/show_image_view_arguments
ui/route/view_arguments/show_video_view_arguments
ui/route/view_arguments/thread_members_view_arguments
ui/route/view_arguments/thread_messages_view_arguments
ui/route/view_arguments/threads_view_arguments
ui/route/view_arguments/view_arguments_base
ui/views/change_info_view
ui/views/contact_details_view
ui/views/contacts_view
ui/views/conversations_view
ui/views/create_group_view
ui/views/current_user_info_view
ui/views/forward_message_select_view
ui/views/forward_messages_view
ui/views/group_add_members_view
ui/views/group_change_owner_view
ui/views/group_delete_members_view
ui/views/group_details_view
ui/views/group_members_view
ui/views/group_mention_view
ui/views/groups_view
ui/views/messages_view
ui/views/new_request_details_view
ui/views/new_requests_view
ui/views/report_message_view
ui/views/search_group_members_view
ui/views/search_history_view
ui/views/search_view
ui/views/select_contact_view
ui/views/show_image_view
ui/views/show_video_view
ui/views/thread_members_view
ui/views/thread_messages_view
ui/views/threads_view
ui/widgets/chat_uikit_alphabetical_widget
ui/widgets/chat_uikit_app_bar
ui/widgets/chat_uikit_avatar
ui/widgets/chat_uikit_badge
ui/widgets/chat_uikit_bottom_sheet
ui/widgets/chat_uikit_button
ui/widgets/chat_uikit_dialog
ui/widgets/chat_uikit_downloads_helper_widget
ui/widgets/chat_uikit_emoji_panel
ui/widgets/chat_uikit_emoji_rich_text_widget
ui/widgets/chat_uikit_list_view
ui/widgets/chat_uikit_message_reaction_info
ui/widgets/chat_uikit_message_status_widget
ui/widgets/chat_uikit_quote_widget
ui/widgets/chat_uikit_reg_exp_text
ui/widgets/chat_uikit_reply_bar
ui/widgets/chat_uikit_search_widget
ui/widgets/chat_uikit_show_image_widget
ui/widgets/chat_uikit_show_video_widget
ui/widgets/edit_bar/chat_uikit_edit_bar
ui/widgets/input_bar/chat_uikit_input_bar
ui/widgets/input_bar/chat_uikit_selection_controls
ui/widgets/input_bar/custom_text_editing_controller
ui/widgets/list_view_items/chat_uikit_alphabetical_list_view_item
ui/widgets/list_view_items/chat_uikit_contact_list_view_item
ui/widgets/list_view_items/chat_uikit_conversation_list_view_item
ui/widgets/list_view_items/chat_uikit_details_list_view_item
ui/widgets/list_view_items/chat_uikit_group_list_view_item
ui/widgets/list_view_items/chat_uikit_list_view_more_item
ui/widgets/list_view_items/chat_uikit_new_request_list_view_item
ui/widgets/list_view_items/chat_uikit_reaction_widget
ui/widgets/list_view_items/chat_uikit_search_list_view_item
ui/widgets/list_view_items/message_list_view_items/chat_uikit_message_bubble_widget
ui/widgets/list_view_items/message_list_view_items/chat_uikit_message_list_view_alert_item
ui/widgets/list_view_items/message_list_view_items/chat_uikit_message_list_view_message_item
ui/widgets/list_view_items/message_list_view_items/chat_uikit_message_reactions_row
ui/widgets/list_view_items/message_list_view_items/chat_uikit_message_thread_widget
ui/widgets/list_view_items/message_list_view_items/message_widget/chat_uikit_card_bubble_widget
ui/widgets/list_view_items/message_list_view_items/message_widget/chat_uikit_combine_bubble_widget
ui/widgets/list_view_items/message_list_view_items/message_widget/chat_uikit_file_bubble_widget
ui/widgets/list_view_items/message_list_view_items/message_widget/chat_uikit_image_bubble_widget
ui/widgets/list_view_items/message_list_view_items/message_widget/chat_uikit_nonsupport_bubble_widget
ui/widgets/list_view_items/message_list_view_items/message_widget/chat_uikit_text_bubble_widget
ui/widgets/list_view_items/message_list_view_items/message_widget/chat_uikit_video_bubble_widget
ui/widgets/list_view_items/message_list_view_items/message_widget/chat_uikit_voice_bubble_widget
ui/widgets/message_bottom_menu/chat_uikit_message_view_bottom_menu
ui/widgets/popup_menu/chat_uikit_popup_menu
ui/widgets/popup_menu/chat_uikit_position_widget
ui/widgets/record_bar/chat_uikit_record_bar
ui/widgets/record_bar/src/error_code
ui/widgets/record_bar/src/record_bar
ui/widgets/record_bar/src/record_data
ui/widgets/record_bar/src/record_error
ui/widgets/record_bar/src/record_types
ui/widgets/record_bar/src/water_ripple
universal/chat_uikit_action_model
universal/chat_uikit_defines
universal/chat_uikit_log
universal/chat_uikit_tools
universal/defines
universal/inner_headers