# UI Kit quickstart (/en/realtime-media/im/get-started-uikit/flutter)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

Instant messaging connects people wherever they are and allows them to communicate with others in real time. Agora offers an open-source Chat UI Kit project on GitHub. With built-in user interfaces for key Chat features, the Agora Chat UI Kit enables you to quickly embed real-time messaging into your app without requiring extra effort on the UI.

<CalloutContainer type="info">
  <CalloutDescription>
    For the latest Agora Chat UIKit documentation, refer to the [UIKit 2.x Documentation](https://github.com/AgoraLab/agora-chat-uikit) GitHub repository.
  </CalloutDescription>
</CalloutContainer>

Legacy UIkit 1.x documentation

This page shows sample code to add peer-to-peer messaging into your app by using the Agora Chat UI Kit.

## Understand the tech

The following figure shows the workflow of how clients send and receive peer-to-peer messages:

Chat UI kit workflow

![Chat UI Kit peer-to-peer messaging workflow](https://assets-docs.agora.io/images/im/ui-kit-quickstart.png)

1. Clients retrieve a token from your app server.
2. Client A and Client B log in to Agora Chat.
3. Client A sends a message to Client B. The message is sent to the Agora Chat server, and the server delivers the message to Client B. When Client B receives the message, the SDK triggers an event. Client B listens for the event and gets the message.

## Prerequisites

* Flutter 2.10 or higher.

* Dart 2.16 or higher.

* If your target platform is iOS:

  * macOS
  * Xcode 12.4 or higher with Xcode Command Line Tools
  * CocoaPods
  * An iOS emulator or a physical iOS device running iOS 10.0 or higher

* If your target platform is Android:

  * macOS or Windows
  * Android Studio 4.0 or higher with JDK 1.8 or higher
  * An Android emulator or a physical Android device running Android SDK API 21 or higher

## Project setup

Follow these steps to create the environment necessary to integrate Chat UI Kit into your app:

1. Create a new Flutter project for iOS and Android

   1. In the terminal, run the following command:

      ```bash
      flutter create uikit_demo --platforms=ios,android -i objc -a java
      ```

   2. Integrate the UI Kit using [pub.dev](https://pub.dev/packages/agora_chat_uikit) (Recommended)

      Execute the following commands in the `uikit_demo` directory:

      ```bash
      cd uikit_demo

      flutter pub add agora_chat_uikit
      flutter pub get
      ```

2. Add permissions for network and device access

   1. For Android, in `/app/Manifests/AndroidManifest.xml`, add the following permissions after `</application>`:

      ```xml
      <uses-permission android:name="android.permission.INTERNET" />
      <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
      <uses-permission android:name="android.permission.WAKE_LOCK"/>
      <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
      <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
      <uses-permission android:name="android.permission.CAMERA"/>
      <uses-permission android:name="android.permission.RECORD_AUDIO"/>
      ```

   2. For iOS, open **info** in the project navigation panel, and add the following properties to the [Property List](https://help.apple.com/xcode/mac/current/#/dev3f399a2a6):

      | Key                                         | Type   | Value                    |
      | ------------------------------------------- | ------ | ------------------------ |
      | `Privacy - Microphone Usage Description`    | String | For microphone access    |
      | `Privacy - Camera Usage Description`        | String | For camera access        |
      | `Privacy - Photo Library Usage Description` | String | For photo library access |

3. Prevent code obfuscation

   In the `example/android/app/proguard-rules.pro` file, add the following lines:

   ```java
   -keep class com.hyphenate.** {*;}
   -dontwarn  com.hyphenate.**
   ```

## Implementation

This section shows how to use the Chat UI Kit to implement peer-to-peer messaging in your app.

The `agora_chat_uikit` library provides the logic and UI to implement the following Chat functions:

* Send, receive, and display messages
* Shows the unread message count, and clear messages. Text, image, emoji, file, and audio messages are supported
* Delete conversations and messages
* Customize the UI

### Chat UI Kit widgets

| Widget                  | Function            | Description                                                                                |
| ----------------------- | ------------------- | ------------------------------------------------------------------------------------------ |
| `ChatUIKit`             | Root widget         | The root of all widgets in Chat UI Kit.                                                    |
| `ChatConversationsView` | Conversation list   | Presents avatar, nickname, last message, unread count, and message time.                   |
| `ChatConversationsView` | Delete conversation | Deletes the conversation from the conversation list.                                       |
| `ChatMessagesView`      | Message sender      | Sends text, emoji, image, file, and voice messages.                                        |
| `ChatMessagesView`      | Delete messages     | Deletes messages.                                                                          |
| `ChatMessagesView`      | Recall message      | Recalls messages sent within 120 seconds.                                                  |
| `ChatMessagesView`      | Read mark           | Receives a read receipt after a message is retrieved.                                      |
| `ChatMessagesView`      | Message sent state  | Displays the status after a message is sent.                                               |
| `ChatMessagesView`      | Display message     | Displays one-to-one and group messages with sender, content, time, and status information. |

Use the following UI Kit widgets to implement Chat functionality with the associated UI in your projects.

#### `ChatUIKit`

Place a `ChatUIKit` widget at the top of your widget tree to help refresh the `ChatConversationsView` when returning from `ChatMessagesView`.

```dart
ChatUIKit({
    super.key,
    this.child,
    ChatUIKitTheme? theme,
});
```

The `ChatUIKit` widget also enables you to customize the theme settings.

| Property | Description                                                                                             |
| -------- | ------------------------------------------------------------------------------------------------------- |
| `theme`  | Chat UI Kit theme for setting component styles. If this property is not set, the default style is used. |

#### `ChatMessagesView`

You use `ChatMessagesView` to manage text, images, emojis, files, and voice messages:

* Send and receive messages
* Delete messages
* Recall messages

| Property                        | Description                                                                                                                                                                                           |
| :------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `conversation`                  | The ChatConversation to which the messages belong.                                                                                                                                                    |
| `inputBarTextEditingController` | Text input widget text editing controller.                                                                                                                                                            |
| `background`                    | The background widget.                                                                                                                                                                                |
| `inputBar`                      | Text input component. If you don't pass in this property, `ChatInputBar` is used by default.                                                                                                          |
| `onTap`                         | Message bubble click callback.                                                                                                                                                                        |
| `onBubbleLongPress`             | Callback for holding a message bubble.                                                                                                                                                                |
| `onBubbleDoubleTap`             | Callback for double-clicking a message bubble.                                                                                                                                                        |
| `avatarBuilder`                 | Avatar component builder.                                                                                                                                                                             |
| `nicknameBuilder`               | Nickname component builder.                                                                                                                                                                           |
| `itemBuilder`                   | Message bubble. If you don't set this property, the default bubble is used.                                                                                                                           |
| `moreItems`                     | Action items displayed after a message bubble is held down. If you return `null` in `onBubbleLongPress`, `moreItems` is used. This property involves three default actions: copy, delete, and recall. |
| `messageListViewController`     | Message list controller. You are advised to use the default value. For details, see `ChatMessageListController`.                                                                                      |
| `willSendMessage`               | Text message pre-sending callback. This callback needs to return a `ChatMessage` object.                                                                                                              |
| `onError`                       | Error callback, such as no permissions.                                                                                                                                                               |
| `enableScrollBar`               | Whether to enable the scroll bar. The scroll bar is enabled by default.                                                                                                                               |
| `needDismissInputWidget`        | Callback for dismissing the input widget. If you use a custom input widget, dismiss the input widget when you receive this callback, for example, by calling `FocusNode.unfocus`. See `ChatInputBar`. |
| `inputBarMoreActionsOnTap`      | The callback for clicking the plus symbol next to the input box. You need to return the `ChatBottomSheetItems` list.                                                                                  |

```dart
ChatMessagesView({
  required this.conversation,
  this.inputBarTextEditingController,
  this.background,
  this.inputBar,
  this.onTap,
  this.onBubbleLongPress,
  this.onBubbleDoubleTap,
  this.avatarBuilder,
  this.nicknameBuilder,
  this.itemBuilder,
  this.moreItems,
  ChatMessageListController? messageListViewController,
  this.willSendMessage,
  this.onError,
  this.enableScrollBar = true,
  this.needDismissInputWidget,
  this.inputBarMoreActionsOnTap,
  super.key,
});
```

#### `ChatConversationsView`

The 'ChatConversationsView' allows you to quickly display and manage the current conversations.

| Property                        | Description                                                                                           |
| :------------------------------ | :---------------------------------------------------------------------------------------------------- |
| `controller`                    | The `ChatConversationsView` controller.                                                               |
| `itemBuilder`                   | Conversation list item builder. Return a widget if you need to customize it.                          |
| `avatarBuilder`                 | Avatar builder. If this property is not implemented or you return `null`, the default avatar is used. |
| `nicknameBuilder`               | Nickname builder. If you don't set this property or return `null`, the conversation ID is displayed.  |
| `onItemTap`                     | The callback of the click event of the conversation list item.                                        |
| `backgroundWidgetWhenListEmpty` | Background widget when the list is empty.                                                             |
| `enablePullReload`              | Whether to enable drop-down refresh.                                                                  |

```dart
ChatConversationsView({
  super.key,
  this.onItemTap,
  ChatConversationsViewController? controller,
  this.itemBuilder,
  this.avatarBuilder,
  this.nicknameBuilder,
  this.backgroundWidgetWhenListEmpty,
  this.enablePullReload = false,
  this.scrollController,
  this.reverse = false,
  this.primary,
  this.physics,
  this.shrinkWrap = false,
  this.cacheExtent,
  this.dragStartBehavior = DragStartBehavior.down,
  this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
  this.restorationId,
  this.clipBehavior = Clip.hardEdge,
});
```

### Local integration

The recommended integration method for the Chat UI Kit is using pub.dev. You can also download the project manually for integration. To do this, open the `uikit_demo` project in your IDE and take the following steps:

1. Clone the [Chat UIKit for Flutter](https://github.com/AgoraIO-Usecase/AgoraChat-UIKit-flutter) repository.

   ```bash
   git clone https://github.com/AgoraIO-Usecase/AgoraChat-UIKit-flutter.git
   ```

2. Add the UI Kit dependency:

   ```yaml
   dependencies:
       agora_chat_uikit:
           path: `<#uikit path#>`
   ```

3. Replace the code in `lib/main.dart` with the following:

   ```dart
   import 'package:flutter/material.dart';
   import 'package:agora_chat_uikit/agora_chat_uikit.dart';

   import 'messages_page.dart';

   class ChatConfig {
     static const String appKey = "";
     static const String userId = "";
     static const String agoraToken = '';
   }

   void main() async {
     assert(ChatConfig.appKey.isNotEmpty,
         "You need to configure AppKey information first.");
     WidgetsFlutterBinding.ensureInitialized();
     final options = ChatOptions(
       appKey: ChatConfig.appKey,
       autoLogin: false,
     );
     await ChatClient.getInstance.init(options);
     runApp(const MyApp());
   }

   class MyApp extends StatelessWidget {
     const MyApp({super.key});
     @override
     Widget build(BuildContext context) {
       return MaterialApp(
         title: 'Flutter Demo',
         theme: ThemeData(
           primarySwatch: Colors.blue,
         ),
         builder: (context, child) {
           // ChatUIKit widget at the top of the widget
           return ChatUIKit(child: child!);
         },
         localizationsDelegates: AppLocalizations.localizationsDelegates,
         supportedLocales: AppLocalizations.supportedLocales,
         home: const MyHomePage(title: 'Flutter Demo'),
       );
     }
   }

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

     final String title;

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

   class _MyHomePageState extends State<MyHomePage> {
     ScrollController scrollController = ScrollController();
     ChatConversation? conversation;
     String _chatId = "";
     final List<String> _logText = [];
     @override
     Widget build(BuildContext context) {
       return Scaffold(
         appBar: AppBar(
           title: Text(widget.title),
         ),
         body: Container(
           padding: const EdgeInsets.only(left: 10, right: 10),
           child: Column(
             crossAxisAlignment: CrossAxisAlignment.stretch,
             mainAxisSize: MainAxisSize.max,
             children: [
               const SizedBox(height: 10),
               const Text("login userId: ${ChatConfig.userId}"),
               const Text("agoraToken: ${ChatConfig.agoraToken}"),
               const SizedBox(height: 10),
               Row(
                 mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                 children: [
                   Expanded(
                     flex: 1,
                     child: ElevatedButton(
                       onPressed: () {
                         _signIn();
                       },
                       child: const Text("SIGN IN"),
                     ),
                   ),
                   const SizedBox(width: 10),
                   Expanded(
                     child: ElevatedButton(
                       onPressed: () {
                         _signOut();
                       },
                       child: const Text("SIGN OUT"),
                     ),
                   ),
                 ],
               ),
               const SizedBox(height: 10),
               Row(
                 children: [
                   Expanded(
                     child: TextField(
                       decoration: const InputDecoration(
                         hintText: "Enter recipient's userId",
                       ),
                       onChanged: (chatId) => _chatId = chatId,
                     ),
                   ),
                   const SizedBox(height: 10),
                   ElevatedButton(
                     onPressed: () {
                       pushToChatPage(_chatId);
                     },
                     child: const Text("START CHAT"),
                   ),
                 ],
               ),
               Flexible(
                 child: ListView.builder(
                   controller: scrollController,
                   itemBuilder: (_, index) {
                     return Text(_logText[index]);
                   },
                   itemCount: _logText.length,
                 ),
               ),
             ],
           ),
         ),
       );
     }

     void pushToChatPage(String userId) async {
       if (userId.isEmpty) {
         _addLogToConsole('UserId is null');
         return;
       }
       if (ChatClient.getInstance.currentUserId == null) {
         _addLogToConsole('user not login');
         return;
       }
       ChatConversation? conv =
           await ChatClient.getInstance.chatManager.getConversation(userId);
       Future(() {
         Navigator.of(context).push(MaterialPageRoute(builder: (_) {
           return MessagesPage(conv!);
         }));
       });
     }

     void _signIn() async {
       _addLogToConsole('begin sign in...');
       if (ChatConfig.agoraToken.isNotEmpty) {
         try {
           await ChatClient.getInstance.loginWithAgoraToken(
             ChatConfig.userId,
             ChatConfig.agoraToken,
           );
         } on ChatError catch (e) {
           _addLogToConsole('sign in fail: ${e.description}');
         }
       } else {
         _addLogToConsole('sign in fail: The agoraToken cannot both be null.');
       }
     }

     void _signOut() async {
       _addLogToConsole('begin sign out...');
       try {
         await ChatClient.getInstance.logout();
         _addLogToConsole('sign out success');
       } on ChatError catch (e) {
         _addLogToConsole('sign out fail: ${e.description}');
       }
     }

     void _addLogToConsole(String log) {
       _logText.add("$_timeString: $log");
       setState(() {
         scrollController.jumpTo(scrollController.position.maxScrollExtent);
       });
     }

     String get _timeString {
       return DateTime.now().toString().split(".").first;
     }
   }
   ```

4. To create a `MessagesPage` refer to the following code:

   ```dart
   import 'package:agora_chat_uikit/agora_chat_uikit.dart';
   import 'package:flutter/material.dart';

   class MessagesPage extends StatefulWidget {
     const MessagesPage(this.conversation, {super.key});

     final ChatConversation conversation;

     @override
     State<MessagesPage> createState() => _MessagesPageState();
   }

   class _MessagesPageState extends State<MessagesPage> {
     @override
     Widget build(BuildContext context) {
       return Scaffold(
         appBar: AppBar(title: Text(widget.conversation.id)),
         body: SafeArea(
           // Message page in uikit.
           child: ChatMessagesView(
             conversation: widget.conversation,
           ),
         ),
       );
     }
   }
   ```

## Test your app

To test the UI Kit Chat features example:

1. In `example/lib/main.dart`, set the `appKey`, `userId`, and `agoraToken` to valid values.

   ```dart
   class ChatConfig {
       static const String appKey = "";
       static const String userId = "";
       static const String agoraToken = '';
   }
   ```

2. Click **Sign In**. You see a log message confirming sign-in success.

3. Run the app on another device or simulator. Ensure that the `userId` values are unique.

4. On the first device or simulator, enter the user id you just created and click **Start Chat**. You can now start chatting between the two clients.

## Reference

Refer to the fully featured [AgoraChat-UIKit-flutter](https://github.com/AgoraIO-Usecase/AgoraChat-UIKit-flutter) demo app as an implementation reference. To customize the UI Kit, refer to the following information.

### Customize colors

You can set the color when adding `ChatUIKit`.

```dart
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      builder: (context, child) {
        // ChatUIKit widget at the top of the widget
        return ChatUIKit(
          // ChatUIKitTheme
          theme: ChatUIKitTheme(),
          child: child!,
        );
      },
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}
```

### Add an avatar

```dart
class _MessagesPageState extends State<MessagesPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.conversation.id)),
      body: SafeArea(
        // Message page in uikit.
        child: ChatMessagesView(
          conversation: widget.conversation,
          avatarBuilder: (context, userId) {
            // Returns the avatar widget that you want to display.
            return Container(
              width: 30,
              height: 30,
              color: Colors.red,
            );
          },
        ),
      ),
    );
  }
}
```

### Add a nickname

```dart
class _MessagesPageState extends State<MessagesPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.conversation.id)),
      body: SafeArea(
        // Message page in uikit.
        child: ChatMessagesView(
          conversation: widget.conversation,
          // Returns the nickname widget that you want to display.
          nicknameBuilder: (context, userId) {
            return Text(userId);
          },
        ),
      ),
    );
  }
}
```

### Add the bubble click event

```dart
class _MessagesPageState extends State<MessagesPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.conversation.id)),
      body: SafeArea(
        // Message page in uikit.
        child: ChatMessagesView(
          conversation: widget.conversation,
          // item tap event
          onTap: (context, message) {
            bubbleClicked(message);
            return true;
          },
        ),
      ),
    );
  }

  void bubbleClicked(ChatMessage message) {
    debugPrint('bubble clicked');
  }
}
```

### Customize the message item widget

```dart
class _MessagesPageState extends State<MessagesPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.conversation.id)),
      body: SafeArea(
        // Message page in uikit.
        child: ChatMessagesView(
          conversation: widget.conversation,
          itemBuilder: (context, model) {
            if (model.message.body.type == MessageType.TXT) {
              // Custom message bubble
              return CustomTextItemWidget(
                model: model,
                onTap: (context, message) {
                  bubbleClicked(message);
                  return true;
                },
              );
            }
          },
        ),
      ),
    );
  }

  void bubbleClicked(ChatMessage message) {
    debugPrint('bubble clicked');
  }
}

// Custom message bubble
class CustomTextItemWidget extends ChatMessageListItem {
  const CustomTextItemWidget({super.key, required super.model, super.onTap});

  @override
  Widget build(BuildContext context) {
    ChatTextMessageBody body = model.message.body as ChatTextMessageBody;

    Widget content = Text(
      body.content,
      style: const TextStyle(
        color: Colors.black,
        fontSize: 50,
        fontWeight: FontWeight.w400,
      ),
    );
    return getBubbleWidget(content);
  }
}

```

### Customize the input widget

```dart
class _MessagesPageState extends State<MessagesPage> {
  late ChatMessageListController _msgController;
  final TextEditingController _textController = TextEditingController();
  final FocusNode _focusNode = FocusNode();
  @override
  void initState() {
    super.initState();
    _msgController = ChatMessageListController(widget.conversation);
  }

  @override
  void dispose() {
    _focusNode.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.conversation.id)),
      body: SafeArea(
        // Message page in uikit.
        child: ChatMessagesView(
          conversation: widget.conversation,
          messageListViewController: _msgController,
          inputBar: customInputWidget(),
          needDismissInputWidget: () {
            _focusNode.unfocus();
          },
        ),
      ),
    );
  }

  // custom input widget
  Widget customInputWidget() {
    return SizedBox(
      height: 50,
      child: Row(
        children: [
          Expanded(
            child: TextField(
              focusNode: _focusNode,
              controller: _textController,
            ),
          ),
          ElevatedButton(
              onPressed: () {
                final msg = ChatMessage.createTxtSendMessage(
                    targetId: widget.conversation.id,
                    content: _textController.text);
                _textController.text = '';
                _msgController.sendMessage(msg);
              },
              child: const Text('Send'))
        ],
      ),
    );
  }
}

```

### Delete all Messages in the current conversation

```dart
class _MessagesPageState extends State<MessagesPage> {
  late ChatMessageListController _msgController;

  @override
  void initState() {
    super.initState();
    _msgController = ChatMessageListController(widget.conversation);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.conversation.id),
        actions: [
          TextButton(
              onPressed: () {
                _msgController.deleteAllMessages();
              },
              child: const Text(
                'Clear',
                style: TextStyle(color: Colors.white),
              ))
        ],
      ),
      body: SafeArea(
        // Message page in uikit.
        child: ChatMessagesView(
          conversation: widget.conversation,
          messageListViewController: _msgController,
        ),
      ),
    );
  }
}
```

### Customize actions displayed upon a click of the plus symbol in the page

```dart
class _MessagesPageState extends State<MessagesPage> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.conversation.id),
      ),
      body: SafeArea(
        // Message page in uikit.
        child: ChatMessagesView(
          conversation: widget.conversation,
          // Returns a list of custom events
          inputBarMoreActionsOnTap: (items) {
            ChatBottomSheetItem item = ChatBottomSheetItem(
              type: ChatBottomSheetItemType.normal,
              onTap: customMoreAction,
              label: 'more',
            );

            return items + [item];
          },
        ),
      ),
    );
  }

  Future<void> customMoreAction() async {
    debugPrint('custom action');
    Navigator.of(context).pop();
  }
}
```

    
  
      
  
