exportChat method

Future<ChatResult<ChatExport>> exportChat(
  1. String roomId, {
  2. int pageSize = 100,
  3. int? maxMessages,
  4. String displayNameFor(
    1. String userId
    )?,
  5. DateFormat? dateFormat,
  6. String mediaPlaceholder = '<media omitted>',
  7. String deletedPlaceholder = 'This message was deleted',
  8. String? roomTitle,
})

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