fetchNext method

Future<List<BaseMessage>> fetchNext({
  1. required dynamic onSuccess(
    1. List<BaseMessage> message
    )?,
  2. required dynamic onError(
    1. CometChatException excep
    )?,
})

Returns a list of BaseMessage object fetched after putting the filters.

Android Reference: MessagesRequest.fetchNext() Migration Note: Migrated from platform channels to native Dart. Uses MessageRepository for backend communication. Fetches newer messages (append direction).

Implementation

Future<List<BaseMessage>> fetchNext(
    {required Function(List<BaseMessage> message)? onSuccess,
    required Function(CometChatException excep)? onError}) async {
  try {
    // 1. Validate request parameters
    // **Android Reference:** MessagesRequest.fetchNext() → validateMessageRequest()
    final validationError = _validateMessageRequest();
    if (validationError != null) {
      if (onError != null) onError(validationError);
      return [];
    }

    // 2. Check ERROR_FILTERS_MISSING — fetchNext requires at least one cursor
    // **Android Reference:** if (messageId == -1 && timestamp == -1 && updatedAfter == -1)
    final int? updatedAfterLong = updatedAfter != null
        ? updatedAfter!.millisecondsSinceEpoch ~/ 1000
        : null;
    if (_messageId == null &&
        _timestamp == null &&
        updatedAfterLong == null) {
      final error = CometChatException(
        'ERR_FILTERS_MISSING',
        'At least one of messageId, timestamp, or updatedAfter must be set for fetchNext',
        'At least one of messageId, timestamp, or updatedAfter must be set for fetchNext',
      );
      if (onError != null) onError(error);
      return [];
    }

    // 3. Check updatesOnly without updatedAfter
    // **Android Reference:** if (updatesOnly && updatedAfter == -1)
    if ((updatesOnly ?? false) && updatedAfterLong == null) {
      final error = CometChatException(
        'ERR_UPDATESONLY_WITHOUT_UPDATEDAFTER',
        'updatedAfter must be set when updatesOnly is true',
        'updatedAfter must be set when updatesOnly is true',
      );
      if (onError != null) onError(error);
      return [];
    }

    // 4. Check hasNext flag — return empty list if no more messages
    // **Android Reference:** MessagesRequest.fetchNext() hasNext check
    if (!_hasNext) {
      if (onSuccess != null) onSuccess([]);
      return [];
    }

    // 5. Check limit does not exceed MAX_LIMIT
    // **Android Reference:** Each fetch method checks limit <= MAX_LIMIT
    if ((limit ?? 0) > (maxLimit ?? 100)) {
      final error = CometChatException(
        'ERR_LIMIT_EXCEEDED',
        'Limit cannot exceed $maxLimit',
        'Limit cannot exceed $maxLimit',
      );
      if (onError != null) onError(error);
      return [];
    }

    // 6. Check inProgress flag — return error if already fetching
    // **Android Reference:** returns ERROR_REQUEST_IN_PROGRESS to onError
    if (_inProgress) {
      final error = CometChatException(
        'ERR_REQUEST_IN_PROGRESS',
        'A fetch request is already in progress',
        'A fetch request is already in progress',
      );
      if (onError != null) onError(error);
      return [];
    }

    // Set inProgress flag before fetch
    // **Android Reference:** MessagesRequest.fetchNext() line: inProgress = true
    _inProgress = true;

    // 1. Get SDK instance
    final sdk = SdkRegistry.getInstance();

    debugPrint(
        "fetchNext - Starting with cursor: messageId=$_messageId, timestamp=$_timestamp");
    debugPrint(
        "fetchNext - uid=$uid, guid=$guid, parentMessageId=$parentMessageId");

    // 2. Get updatedAfter as Unix timestamp (already computed above)
    int? updateAfterLong = updatedAfterLong;

    // 3. Call repository based on parentMessageId/uid/guid
    // **Android Reference:** MessagesRequest.fetchNext() routing:
    //   parentMessageId first, then uid/guid, then fallback
    late final result;

    if (parentMessageId != null && parentMessageId != 0) {
      // Threaded messages — checked FIRST per Android SDK
      result = await sdk.messages.getThreadedMessages(
        parentMessageId.toString(),
        limit: limit ?? 30,
        affix: 'append', // fetchNext = append (newer messages)
        timestamp: _timestamp,
        messageId: _messageId?.toString(),
        unread: unread,
        hideMessagesFromBlockedUsers: hideMessagesFromBlockedUsers,
        searchKeyword: searchKeyword,
        updatedAfter: updateAfterLong,
        updatesOnly: updatesOnly,
        categories: categories,
        types: types,
        hideDeleted: hideDeleted,
        tags: tags,
        withTags: withTags,
        interactionGoalCompleted: interactionGoalCompletedOnly,
        mentionsWithTagInfo: _mentionsWithTagInfo,
        mentionsWithBlockedInfo: _mentionsWithBlockedInfo,
        hasAttachments: hasAttachments,
        hasLinks: hasLinks,
        hasMentions: hasMentions,
        hasReactions: hasReactions,
        mentionedUids: mentionedUids,
        attachmentTypes: attachmentTypes,
        withParent: withParent,
        hideQuotedMessages: hideQuotedMessages,
      );
    } else if (uid != null &&
        uid!.isNotEmpty &&
        guid != null &&
        guid!.isNotEmpty) {
      // User messages in a specific group
      // **Android Reference:** fetchMessagesForUserInGroup()
      result = await sdk.messages.getUserMessages(
        uid!,
        limit: limit ?? 30,
        affix: 'append',
        timestamp: _timestamp,
        messageId: _messageId?.toString(),
        unread: unread,
        hideMessagesFromBlockedUsers: hideMessagesFromBlockedUsers,
        searchKeyword: searchKeyword,
        updatedAfter: updateAfterLong,
        updatesOnly: updatesOnly,
        categories: categories,
        types: types,
        hideReplies: hideReplies,
        hideDeleted: hideDeleted,
        tags: tags,
        withTags: withTags,
        interactionGoalCompleted: interactionGoalCompletedOnly,
        mentionsWithTagInfo: _mentionsWithTagInfo,
        mentionsWithBlockedInfo: _mentionsWithBlockedInfo,
        hasAttachments: hasAttachments,
        hasLinks: hasLinks,
        hasMentions: hasMentions,
        hasReactions: hasReactions,
        mentionedUids: mentionedUids,
        attachmentTypes: attachmentTypes,
        hideQuotedMessages: hideQuotedMessages,
      );
    } else if (uid != null && uid!.isNotEmpty) {
      // User messages
      result = await sdk.messages.getUserMessages(
        uid!,
        limit: limit ?? 30,
        affix: 'append', // fetchNext = append (newer messages)
        timestamp: _timestamp,
        messageId: _messageId?.toString(),
        unread: unread,
        hideMessagesFromBlockedUsers: hideMessagesFromBlockedUsers,
        searchKeyword: searchKeyword,
        updatedAfter: updateAfterLong,
        updatesOnly: updatesOnly,
        categories: categories,
        types: types,
        hideReplies: hideReplies,
        hideDeleted: hideDeleted,
        tags: tags,
        withTags: withTags,
        interactionGoalCompleted: interactionGoalCompletedOnly,
        mentionsWithTagInfo: _mentionsWithTagInfo,
        mentionsWithBlockedInfo: _mentionsWithBlockedInfo,
        hasAttachments: hasAttachments,
        hasLinks: hasLinks,
        hasMentions: hasMentions,
        hasReactions: hasReactions,
        mentionedUids: mentionedUids,
        attachmentTypes: attachmentTypes,
        hideQuotedMessages: hideQuotedMessages,
      );
    } else if (guid != null && guid!.isNotEmpty) {
      // Group messages
      result = await sdk.messages.getGroupMessages(
        guid!,
        limit: limit ?? 30,
        affix: 'append', // fetchNext = append (newer messages)
        timestamp: _timestamp,
        messageId: _messageId?.toString(),
        unread: unread,
        hideMessagesFromBlockedUsers: hideMessagesFromBlockedUsers,
        searchKeyword: searchKeyword,
        updatedAfter: updateAfterLong,
        updatesOnly: updatesOnly,
        categories: categories,
        types: types,
        hideReplies: hideReplies,
        hideDeleted: hideDeleted,
        tags: tags,
        withTags: withTags,
        interactionGoalCompleted: interactionGoalCompletedOnly,
        mentionsWithTagInfo: _mentionsWithTagInfo,
        mentionsWithBlockedInfo: _mentionsWithBlockedInfo,
        hasAttachments: hasAttachments,
        hasLinks: hasLinks,
        hasMentions: hasMentions,
        hasReactions: hasReactions,
        mentionedUids: mentionedUids,
        attachmentTypes: attachmentTypes,
        hideQuotedMessages: hideQuotedMessages,
      );
    } else {
      // All messages
      result = await sdk.messages.getMessages(
        limit: limit ?? 30,
        timestamp: _timestamp,
        messageId: _messageId?.toString(),
        affix: 'append', // fetchNext = append (newer messages)
        unread: unread,
        hideMessagesFromBlockedUsers: hideMessagesFromBlockedUsers,
        searchKeyword: searchKeyword,
        updatedAfter: updateAfterLong,
        updatesOnly: updatesOnly,
        categories: categories,
        types: types,
        hideReplies: hideReplies,
        hideDeleted: hideDeleted,
        tags: tags,
        withTags: withTags,
        interactionGoalCompleted: interactionGoalCompletedOnly,
        mentionsWithTagInfo: _mentionsWithTagInfo,
        mentionsWithBlockedInfo: _mentionsWithBlockedInfo,
        hasAttachments: hasAttachments,
        hasLinks: hasLinks,
        hasMentions: hasMentions,
        hasReactions: hasReactions,
        mentionedUids: mentionedUids,
        attachmentTypes: attachmentTypes,
        hideQuotedMessages: hideQuotedMessages,
      );
    }

    // 4. Update pagination cursor for next fetch
    // For append (fetchNext), we update to the MAX messageId/timestamp
    // Reference: Android SDK updateMessageIdAndTimestamp() method
    _updatePaginationCursor(result.messages, isAppend: true);

    debugPrint(
        "fetchNext - Updated cursor: messageId=$_messageId, timestamp=$_timestamp");

    // 5. Update pagination state from response
    // **Android Reference:** MessagesRequest.fetchNext() pagination state update
    _updatePaginationState(result);

    // 6. Update hasNext/hasPrevious flags based on page comparison
    // **Android Reference:** if (affix==APPEND && currentPage==totalPages) { hasNext=false; hasPrevious=true; }
    if (_currentPage != -1 &&
        _totalPages != -1 &&
        _currentPage == _totalPages) {
      _hasNext = false;
      _hasPrevious = true;
    }
    // Also set hasNext=false if no messages returned (safety net)
    if (result.messages.isEmpty) {
      _hasNext = false;
    }

    // 7. Reset inProgress flag after successful fetch
    // **Android Reference:** MessagesRequest.fetchNext() line: inProgress = false
    _inProgress = false;

    // 8. Call callbacks
    if (onSuccess != null) {
      onSuccess(result.messages);
    }
    return result.messages;
  } on sdk_errors.SdkException catch (sdkEx) {
    // Reset inProgress flag on error
    _inProgress = false;
    // Convert SdkException to CometChatException
    final cometChatEx = CometChatException(
      sdkEx.code,
      sdkEx.details ?? sdkEx.message,
      sdkEx.message,
    );
    if (onError != null) {
      onError(cometChatEx);
    }
  } catch (e) {
    // Reset inProgress flag on error
    _inProgress = false;
    debugPrint("Error: $e");
    // Handle unexpected errors
    final cometChatEx = CometChatException(
      ErrorCode.errorUnhandledException,
      e.toString(),
      e.toString(),
    );
    if (onError != null) {
      onError(cometChatEx);
    }
  }
  return [];
}