Message component
The message component of the In-app Chat Kit provides the message list and message transmission features.
- Message list: Allow you to view the message history of a chat.
- Message transmission: Allow you to send or receive one-to-one messages and group messages.

Integrate the message component into your project
Prerequisites
Integrate the In-app Chat Kit SDK into your project (finished the initialization and login are required). For more information, see Quick start.
Add the MessageList component
In the file that needs to use the MessageList
component, use the ZIMKitMessageListPage
widget.
(The ZIMKitMessageListPage
widget consists of the appBar
, ZIMKitMessageListView
component, and the ZIMKitMessageInput
component.)
import 'package:zego_zimkit/zego_zimkit.dart';
class YourMessagePage extends StatelessWidget {
const YourMessagePage({Key? key, required this.conversationID, required this.conversationType})
: super(key: key);
final String conversationID;
final ZIMConversationType conversationType;
@override
Widget build(BuildContext context) {
return ZIMKitMessageListPage(
conversationID: conversationID,
conversationType: conversationType,
);
}
}
Customize features/UI
If the default message-relevant features, behaviors or UI don’t fully meet your needs, we allow you to flexibly customize those through the parameters provided by the ZIMKitMessageListView
and ZIMKitMessageInput
mentioned in this section.
ZIMKitMessageListPage
has all the parameters ofZIMKitMessageListView
andZIMKitMessageInput
, so you can pass any parameters toZIMKitMessageListView
andZIMKitMessageInput
throughZIMKitMessageListPage
.
The usage of commonly used parameters is as follows:
The UI of each message can be customized, You can use the messageItemBuilder or messageContentBuilder to build your custom message widget.
messageContentBuilder:
In most cases, you may only need to customize the message body, while keeping the avatar or the send status icon next to the message. to do so, you can use the messageContentBuilder
. The parameter types are as follows:
final Widget Function(BuildContext context, ZIMKitMessage message, Widget defaultWidget)? messageContentBuilder;
You can use it like this.
ZIMKitMessageListPage(
conversationID: conversation.id,
conversationType: conversation.type,
messageContentBuilder: (context, message, defaultWidget) {
if (message.type == ZIMMessageType.custom) {
return RedEnvelopeMessage(message: message);
} else {
return defaultWidget;
}
},
);
messageItemBuilder:
ZIMKitMessageListView is essentially a listView. If you need to customize the drawing of the avatar, message body, sender's name, and sending status, you can use the messageItemBuilder."
The parameter types are as follows:
final Widget Function(BuildContext context, ZIMKitMessage message, Widget defaultWidget)? messageItemBuilder;
In addition to fully customizing the message item, you can also change the primaryColor
of the default message widget to yellow. The code is as follows:
ZIMKitMessageListPage(
conversationID: conversation.id,
conversationType: conversation.type,
messageItemBuilder: (context, message, defaultWidget) {
return Theme(
data: ThemeData(primaryColor: Colors.yellow),
child: defaultWidget,
);
},
);
To customize the press and long press events for a message, use the onMessageItemPressed
and onMessageItemLongPress
. The parameter types are as follows:
final void Function(BuildContext context, ZIMKitMessage message, Function defaultAction)? onMessageItemPressed;
final void Function(BuildContext context, LongPressStartDetails details, ZIMKitMessage message, Function defaultAction)? onMessageItemLongPress;
For example, you can implement functions such as a full-screen preview of images and file download by customizing click and long-press events like this:
ZIMKitMessageListPage(
conversationID: conversation.id,
conversationType: conversation.type,
onMessageItemLongPress: (context, details, message, defaultAction) {
debugPrint('onMessageItemLongPress: ${message.hashCode}');
defaultAction();
},
onMessageItemPressed: (context, message, defaultAction) {
debugPrint('onMessageItemPressed: ${message.hashCode}');
defaultAction();
},
);
To customize the app bar, you can use appBarActions
or appBarBuilder
. The parameter types are as follows:
final List<Widget>? appBarActions;
final AppBar? Function(BuildContext context, AppBar defaultAppBar)? appBarBuilder;
For example, adding a call button to the app bar, and the preview effect is as follows:

Here are two ways to achieve this:
- To add a few buttons to the app bar, use the
appBarActions
parameter. The sample code is as follows:
ZIMKitMessageListPage(
conversationID: conversation.id,
conversationType: conversation.type,
appBarActions: [
IconButton(icon: const Icon(Icons.local_phone), onPressed: () {}),
IconButton(icon: const Icon(Icons.videocam), onPressed: () {}),
],
);
- To fully customize the app bar, use the
appBarBuilder
parameter. The sample code is as follows:
ZIMKitMessageListPage(
conversationID: conversation.id,
conversationType: conversation.type,
appBarBuilder: (context, defaultAppBar) {
return AppBar(
title: Row(
children: [
CircleAvatar(child: conversation.icon),
const SizedBox(width: 15),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(conversation.name, style: const TextStyle(fontSize: 16), overflow: TextOverflow.clip),
Text(conversation.id, style: const TextStyle(fontSize: 12), overflow: TextOverflow.clip)
],
)
],
),
actions: [
IconButton(icon: const Icon(Icons.local_phone), onPressed: () {}),
IconButton(icon: const Icon(Icons.videocam), onPressed: () {}),
],
);
},
);
- If you don't need to show the app bar, you can return null in the
appBarBuilder
.
ZIMKitMessageListPage(
conversationID: conversation.id,
conversationType: conversation.type,
appBarBuilder: (context, defaultAppBar) {
return null;
},
);
The input field uses the TextField
component in Flutter.
- To customize the decoration style of the
TextField
, pass theInputDecoration
parameter to the component through theMessageListPage
. The following shows the sample code and effect:
ZIMKitMessageListPage(
conversationID: conversation.id,
conversationType: conversation.type,
inputDecoration: const InputDecoration(hintText: 'Type message here...'),
);

- Also support these commonly used TextFiled parameters:
Config | Default value |
---|---|
messageInputKeyboardType | TextInputType.multiline |
messageInputMaxLines | 3 |
messageInputMinLines | 1 |
messageInputTextInputAction | TextInputAction.newline |
messageInputTextCapitalization | TextCapitalization.sentences |
- In addition,
TextField
is wrapped by a container, and you can also configure the container additionally. Here are the supported configurations and their default values.
-
messageInputContainerPadding
, The default value isconst EdgeInsets.symmetric(horizontal: 20, vertical: 10)
-
messageInputContainerDecoration
, The default value is:BoxDecoration( color: Theme.of(context).scaffoldBackgroundColor, boxShadow: [ BoxShadow( offset: const Offset(0, 4), blurRadius: 32, color: Theme.of(context).primaryColor.withOpacity(0.15), ), ], )
You can customize the input background style using inputBackgroundDecoration
.
The following shows the sample code:
ZIMKitMessageListPage(
conversationID: conversation.id,
conversationType: conversation.type,
inputBackgroundDecoration: BoxDecoration(borderRadius: BorderRadius.circular(40)),
);
- To hide the default buttons, use the following:
showPickFileButton
: whether to show thePickFileButton
, which is shown by default.showPickMediaButton
: whether to show thePickMediaButton
, which is shown by default.

Sample code:
ZIMKitMessageListPage(
conversationID: conversation.id,
conversationType: conversation.type,
showPickFileButton: false,
showPickMediaButton: true,
);
- To customize the style of default buttons, use the following:
sendButtonWidget
: customize the style of the send button.pickMediaButtonWidget
: customize the style of the media pick button.pickFileButtonWidget
: customize the style of the file pick button.
Sample code:
ZIMKitMessageListPage(
conversationID: conversation.id,
conversationType: conversation.type,
showPickFileButton: true,
showPickMediaButton: true,
sendButtonWidget: const Icon(Icons.send),
pickMediaButtonWidget: const Icon(Icons.photo_library),
pickFileButtonWidget: const Icon(Icons.attach_file),
);
- To add your customized buttons, use the
messageInputActions
.
For example, to add a microphone button and an emoji button on the left side of the input field, use the following sample code:
ZIMKitMessageListPage(
conversationID: conversation.id,
conversationType: conversation.type,
messageInputActions: [
ZIMKitMessageInputAction.left(
IconButton(icon: const Icon(Icons.mic), onPressed: () {}),
),
ZIMKitMessageInputAction.leftInside(
IconButton(icon: const Icon(Icons.sentiment_satisfied_alt_outlined), onPressed: () {}),
),
],
);
As shown in the sample code, you can specify the position of the custom button relative to the
TextField
by usingZIMKitMessageInputAction.left
,ZIMKitMessageInputAction.right
,ZIMKitMessageInputAction.leftInside
, andZIMKitMessageInputAction.rightInside
.
Its position is as follows:

You can customize the message list background using inputBackgroundDecoration
.
The following shows the sample code:
ZIMKitMessageListPage(
conversationID: conversation.id,
conversationType: conversation.type,
messageListBackgroundBuilder: (context, defaultWidget) {
return YourCustomBackgroundWidget();
},
);
Support for the following features when sending voice messages:
- Swipe left to cancel sending voice messages
- Long press and swipe up to lock and continue recording after releasing the finger
- Display countdown for voice recording
![]() | ![]() |
You can set showRecordButton
to false if you don't need the voice messaging feature.
ZIMKitMessageListPage(
showRecordButton: false,
);
In addition, some callbacks have been provided as follows.:
-
void Function(int errorCode)? onFailed
: This callback will be triggered when the message fails to send. -
void Function(int remainingSecond)? onCountdownTick
: The callback will be triggered every second after starting the countdown. When the countdown reaches zero, it will automatically send the voice message.
ZIMKitMessageListPage(
events: ZIMKitMessageListPageEvents(
audioRecord: ZIMKitAudioRecordEvents(
// !mark
onFailed: (int errorCode) {
/// audio message's error list: https://doc-preview-zh.zego.im/article/20148
debugPrint('onRecordFailed: $errorCode');
},
// !mark
onCountdownTick: (int remainingSecond) {
debugPrint('onCountdownTick: $remainingSecond');
},
),
),
);
You can customize the message-list load error using YourCustomErrorWidget
.
The following shows the sample code:
ZIMKitMessageListPage(
conversationID: conversation.id,
conversationType: conversation.type,
messageListErrorBuilder: (context, defaultWidget) {
return YourCustomErrorWidget();
},
);
You can customize the message loading using YourCustomLoadingWidget
.
The following shows the sample code:
ZIMKitMessageListPage(
conversationID: conversation.id,
conversationType: conversation.type,
messageListLoadingBuilder: (context, defaultWidget) {
return YourCustomLoadingWidget();
},
);
You can customize the message before sent using preMessageSending
.
The following shows the sample code:
ZIMKitMessageListPage(
conversationID: conversation.id,
conversationType: conversation.type,
preMessageSending: (ZIMKitMessage message) {
return message;
},
);
Message-related methods
The following methods can be called using ZIMKit()
syntax, such as ZIMKit().deleteMessage()
.
deleteMessage
: Only delete local messages, after deletion, you cannot see the message yourself.recallMessage
: Recall messages, can recall your own messages, or in a group chat, the group owner can recall messages from other members, after recall, users in the conversation cannot see the message anymore.
By default, In-app Chat Kit supports recalling messages sent in 2 minutes. If you want to modify the default configuration (It can be configured to recall messages within 24 hours.), contact ZEGOCLOUD technical support.
Refer to the example code in _onMessageItemLongPress
:
class ZIMKitDemoHomePage extends StatelessWidget {
const ZIMKitDemoHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: Scaffold(
appBar: AppBar(
title: const Text('Conversations'),
actions: const [HomePagePopupMenuButton()],
),
body: ZIMKitConversationListView(
onPressed: (context, conversation, defaultAction) {
Navigator.push(context, MaterialPageRoute(
builder: (context) {
return ZIMKitMessageListPage(
conversationID: conversation.id,
conversationType: conversation.type,
onMessageItemLongPress: _onMessageItemLongPress,
);
},
));
},
),
),
);
}
Future<void> _onMessageItemLongPress(
BuildContext context,
LongPressStartDetails details,
ZIMKitMessage message,
Function defaultAction,
) async {
showCupertinoDialog(
context: context,
barrierDismissible: true,
builder: (context) {
return CupertinoAlertDialog(
title: const Text('Confirme'),
content: const Text('Delete or recall this message?'),
actions: [
CupertinoDialogAction(
onPressed: Navigator.of(context).pop,
child: const Text('Cancel'),
),
CupertinoDialogAction(
onPressed: () {
ZIMKit().deleteMessage([message]);
Navigator.pop(context);
},
child: const Text('Delete'),
),
CupertinoDialogAction(
onPressed: () {
ZIMKit().recallMessage(message).catchError((error) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(error.toString())),
);
});
Navigator.pop(context);
},
child: const Text('Recall'),
),
],
);
},
);
}
}
The default effect when a message is recalled:

To hide the prompt for a recalled message ("Recalled a message" in the above image) or to customize the prompt style for recalling messages, you can use messageItemBuilder
for customization.
Here is an example code snippet to hide the display of recalled messages:
class ZIMKitDemoHomePage extends StatelessWidget {
const ZIMKitDemoHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: Scaffold(
appBar: AppBar(
title: const Text('Conversations'),
actions: const [HomePagePopupMenuButton()],
),
body: ZIMKitConversationListView(
onPressed: (context, conversation, defaultAction) {
Navigator.push(context, MaterialPageRoute(
builder: (context) {
return ZIMKitMessageListPage(
conversationID: conversation.id,
conversationType: conversation.type,
messageItemBuilder: (context, message, defaultWidget) {
// ***** hide the display of recalled messages
if (message.type == ZIMMessageType.revoke) {
return const SizedBox.shrink();
} else {
return defaultWidget;
}
},
);
},
));
},
),
),
);
}
}
You can use ZIMKit().deleteAllMessage() to delete all messages in this conversation.
If you only want to delete the message on this device, you can set isAlsoDeleteFromServer to false.
ZIMKit().deleteAllMessage(
conversationID: conversationID,
conversationType: conversationType,
isAlsoDeleteFromServer: true,
);

- How to send custom messages.
You can use ZIMKit().sendCustomMessage()
to send your custom message, which can be a red envelope, gift, vote, or any other type of message.
What you need to do is define your message protocol, encapsulating customType
and customMessage
. Generally, you can define customMessage
as JSON format
and further expand your message protocol.
ZIMKit().sendCustomMessage(
conversationID,
conversationType,
customType: DemoCustomMessageType.redEnvelope.index,
customMessage: jsonEncode({'count': 10, 'money': 100}),
);
- How to render custom messages.
The UI of each message can be customized, You can use the messageItemBuilder or messageContentBuilder to build your custom message widget.
messageContentBuilder:
In most cases, you may only need to customize the message body, while keeping the avatar or the send status icon next to the message. to do so, you can use the messageContentBuilder
. The parameter types are as follows:
final Widget Function(BuildContext context, ZIMKitMessage message, Widget defaultWidget)? messageContentBuilder;
You can use it like this.
ZIMKitMessageListPage(
conversationID: conversation.id,
conversationType: conversation.type,
messageContentBuilder: (context, message, defaultWidget) {
if (message.type == ZIMMessageType.custom &&
message.customContent!.type == DemoCustomMessageType.redEnvelope.index) {
return RedEnvelopeMessage(message: message);
} else {
return defaultWidget;
}
},
);
messageItemBuilder:
ZIMKitMessageListView is essentially a listView. If you need to customize the drawing of the avatar, message body, sender's name, and sending status, you can use the messageItemBuilder."
The parameter types are as follows:
final Widget Function(BuildContext context, ZIMKitMessage message, Widget defaultWidget)? messageItemBuilder;
In addition to fully customizing the message item, you can also change the primaryColor
of the default message widget to yellow. The code is as follows:
ZIMKitMessageListPage(
conversationID: conversation.id,
conversationType: conversation.type,
messageItemBuilder: (context, message, defaultWidget) {
return Theme(
data: ThemeData(primaryColor: Colors.yellow),
child: defaultWidget,
);
},
);
- How to update extended data.
You can use ZIMKit().updateLocalExtendedData
to update message's local extended data.
Message extended data can be used to display the business logic carried by the messages.
ZIMKit().updateLocalExtendedData(message, 'localExtendedData');
- How to use it or render it.
You can use ValueListenableBuilder
to listen to the value of message.localExtendedData
at any place where you can access the zimkit message, or you can directly read the current value using message.localExtendedData.value
.
ZIMKitMessageListPage(
conversationID: conversation.id,
conversationType: conversation.type,
messageItemBuilder: (context, message, defaultWidget) {
// ....
final extendedDataWidget = ValueListenableBuilder(
valueListenable: message.localExtendedData,
builder: (BuildContext context, String localExtendedData, Widget? child) {
return Text(localExtendedData);
},
),
// ...
return ...;
},
);