Appearance
Flutter SDK
Headless client for your own UI. Prefer widgets? Use the Flutter UI Kit.
Before code: mint a session token on your server. Never ship sk_* in the app.
Guided path: Quick start → Flutter SDK.
Fast path
Do this in order — most integrations are these five steps.
1. Install
bash
flutter pub add sendsar_chat2. Connect
dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:sendsar_chat/sendsar_chat.dart';
Future<SessionResponse> fetchChatSession() async {
final res = await http.post(
Uri.parse('https://your-app.com/api/chat/session'),
headers: {'Cookie': '…'},
);
if (res.statusCode < 200 || res.statusCode >= 300) {
throw Exception(res.body);
}
return SessionResponse.fromJson(jsonDecode(res.body) as Map<String, dynamic>);
}
final session = await fetchChatSession();
final client = Sendsar.init(SendsarInitOptions(apiUrl: session.apiUrl));
await client.connect(ConnectOptions(
userId: session.chatUserId,
token: session.token,
));3. Conversation list (inbox)
dart
final rooms = await client.listRooms();
setConversations(rooms); // ConversationListPage
client.on<Message>(SocketEvent.newMessage, (msg) {
bumpConversation(msg); // preview, time, sort, unread
});4. Message thread (open room)
Create in ChatPage; destroy in dispose.
dart
final sub = createRoomSubscription(
client,
RoomSubscriptionOptions(
roomId: openRoomId,
userId: session.chatUserId,
onInitialMessages: (messages, peerLastReadAt, [nextCursor]) {
setMessages(messages);
},
onMessage: (msg) {
appendById(msg);
bumpConversation(msg);
},
onMessageUpdated: (msg) => replaceById(msg),
),
);
// dispose:
sub.destroy();5. Send
dart
final sent = await client.sendMessage(openRoomId, SendMessageParams(
parts: [MessagePart(type: 'text', text: 'Hello')],
));
appendById(sent);
bumpConversation(sent);Screens (what to update)
| Screen | Shows | Your widget |
|---|---|---|
| Conversation list | Rooms, last message, unread | ConversationListPage |
| Message thread | Bubbles + composer for one roomId | ChatPage / MessageList |
Also common: Room info, Call UI.
| Happens | Conversation list | Message thread |
|---|---|---|
createRoom | Add row | Optional: open it |
| New message | Bump preview / unread | Append if that room is open |
| Edit / delete / reaction / pin | Preview if latest | Replace by id |
forwardMessage | Bump targets | Append in open target |
deleteConversation | Remove row | Pop if open |
clearHistory | Clear preview | Empty list |
| Members change | Optional subtitle | Room info |
| Incoming call | Optional badge | Call UI |
WARNING
messageUpdated → replace by id. Never append a second bubble.
Messaging APIs
Create room & send text
dart
final room = await client.createRoom(CreateRoomParams(
externalId: 'dm:${session.chatUserId}:user_bob',
participants: [
CreateRoomParticipant(id: session.chatUserId),
CreateRoomParticipant(id: 'user_bob'),
],
));
// → Conversation list: add row
final sent = await client.sendMessage(room.id, SendMessageParams(
parts: [MessagePart(type: 'text', text: 'Hello')],
));
// → Thread: append · List: bump · Event: newMessageEdit, delete, reply
dart
await client.updateMessage(roomId, messageId, UpdateMessageParams(
parts: [MessagePart(type: 'text', text: 'Updated')],
)); // → replace · messageUpdated
await client.deleteMessage(roomId, messageId);
// → replace (deletedAt)
await client.sendMessage(roomId, SendMessageParams(
parts: [MessagePart(type: 'text', text: 'Replying!')],
parentMessageId: messageId,
)); // → append · newMessageReactions
dart
await client.toggleReaction(
roomId,
messageId,
ToggleReactionParams(emoji: '👍'),
); // → replace (reactions) · messageUpdatedPin & forward
dart
await client.pinMessage(roomId, messageId);
await client.unpinMessage(roomId, messageId);
await client.forwardMessage(roomId, messageId, ForwardMessageParams(
targetRoomIds: [otherRoomId],
)); // → target rooms: newMessage + bump listSend files
dart
final message = await client.sendFileMessage(
roomId,
bytes: fileBytes,
filename: 'photo.jpg',
mediaType: 'image/jpeg',
); // → append · hydrate URLs for previewsMembers & conversation
dart
await client.addParticipant(roomId, AddParticipantParams(userId: otherUserId));
await client.removeParticipant(roomId, otherUserId);
client.on(SocketEvent.roomParticipantsChanged, (ev) {
if (ev.roomId == roomId) refreshMembers();
});
await client.deleteConversation(roomId);
await client.clearHistory(roomId);Voice & video (optional)
Enable in the dashboard, then flutter pub add sendsar_call (+ mic/camera permissions).
dart
import 'package:sendsar_call/sendsar_call.dart';
final calls = CallClient(CallClientOptions(chat: client));
calls.on('incoming', (invite) => showIncomingCall(invite));
calls.on('localTrack', (e) { /* bind */ });
calls.on('remoteTrack', (e) { /* bind */ });
await calls.start(room.id, const CallStartOptions(type: 'video'));Checklist
- [ ] Session from your BFF — no
sk_*in the app - [ ] Conversation list:
listRooms+newMessagebumps - [ ] Message thread:
createRoomSubscription(destroy on leave) - [ ]
messageUpdated→ replace byid - [ ] Files: hydrate access URLs for previews
- [ ] Calls (if needed):
sendsar_call+ dashboard flag