exportChat method
Exports the full history of roomId to a WhatsApp-style plain-text
transcript.
Pages backward through messages.list until the history is exhausted
(or maxMessages is reached), resolves sender display names through
the adapter's user cache, and returns the formatted ChatExport. Pure
read — no mutation and no new dependency; the host writes the text to a
file and shares it (see ChatExport).
Lines look like 12/06/26, 14:02 - Alice: Hello. Deleted messages and
media (which have no text body) render with the localizable
deletedPlaceholder / mediaPlaceholder (attachment file names are
used when present). Override displayNameFor to control the name
column, or dateFormat for a different timestamp format.
roomTitle, when non-null and non-empty, prepends a Chat: $roomTitle header line (plus a blank line) before the transcript and
is echoed back on ChatExport.roomTitle. null (default) keeps the
transcript exactly as before this parameter existed — the SDK doesn't
resolve a title itself (it has no opinion on room naming) nor does it
prepend any app/branding name; a host wanting that composes it from
ChatExport.roomTitle/ChatExport.text on its side.
Implementation
Future<ChatResult<ChatExport>> exportChat(
String roomId, {
int pageSize = 100,
int? maxMessages,
String Function(String userId)? displayNameFor,
DateFormat? dateFormat,
String mediaPlaceholder = '<media omitted>',
String deletedPlaceholder = 'This message was deleted',
String? roomTitle,
}) async {
if (_a._disposed) {
return ChatSuccess(
ChatExport(
roomId: roomId,
text: '',
messageCount: 0,
roomTitle: roomTitle,
),
);
}
final byId = <String, ChatMessage>{};
// Opaque older-history cursor: `null` on the first page (server returns the
// most recent page), then the response `prevCursor` to page backward.
String? olderCursor;
String? previousCursor;
while (maxMessages == null || byId.length < maxMessages) {
final limit = maxMessages == null
? pageSize
: (maxMessages - byId.length).clamp(1, pageSize);
final result = await _a.client.messages.list(
roomId,
pagination: ChatCursorPaginationParams(
cursor: olderCursor,
direction: olderCursor == null ? null : ChatCursorDirection.older,
limit: limit,
),
cachePolicy: CachePolicy.networkOnly,
);
if (result.isFailure) return result.castFailure<ChatExport>();
final page = result.dataOrThrow;
final items = page.items;
if (items.isEmpty) break;
for (final m in items) {
byId[m.id] = m;
}
// Page backward using the older anchor the server returned for this page.
previousCursor = olderCursor;
olderCursor = page.prevCursor;
// Stop when the server reports no older history, hands back no older
// cursor, or a non-advancing cursor (defensive against backend bugs).
if (!page.hasMore ||
olderCursor == null ||
olderCursor == previousCursor) {
break;
}
}
final resolve = displayNameFor ?? _a.displayNameFor;
final df = dateFormat ?? DateFormat('dd/MM/yy, HH:mm');
final ordered = byId.values.toList()
..sort((a, b) => a.timestamp.compareTo(b.timestamp));
final buffer = StringBuffer();
if (roomTitle != null && roomTitle.isNotEmpty) {
buffer.writeln('Chat: $roomTitle');
buffer.writeln();
}
for (final m in ordered) {
final String body;
final text = m.text?.trim();
if (m.isDeleted) {
body = deletedPlaceholder;
} else if (text != null && text.isNotEmpty) {
body = m.text!;
} else if (m.messageType.hasAttachment ||
m.messageType == MessageType.attachment) {
body = m.fileName ?? mediaPlaceholder;
} else {
body = mediaPlaceholder;
}
buffer.writeln(
'${df.format(m.timestamp.toLocal())} - ${resolve(m.from)}: $body',
);
}
return ChatSuccess(
ChatExport(
roomId: roomId,
text: buffer.toString(),
messageCount: ordered.length,
roomTitle: roomTitle,
),
);
}