Skip to content

Chatroom not cleaning the history/ Duplicated message on Flutter #414

@CaptainJack007

Description

@CaptainJack007

I will explain you the problem with simple words. When I send a message "hello" to user1, and then change the chatroom, if I send a message "Good Morning" to user2, after sending this message, I will see my "Hello" message on the screen too. but that message doesn't belong to this chatroom. When I reload the chatroom, then it will show correctly.

here is my Sockit_Logic:

class SocketLogic extends GetxController {
  late SocketIOUtility _socketUtility;
  late Timer heartbeatTimer;

  final fileUploadLogic = Get.put(FileUploadLogic());

  RxList<MessageHistory> messageHistory = <MessageHistory>[].obs;
  RxList<SocketOnlineUsers> onlineUsers = <SocketOnlineUsers>[].obs;
  RxList<SocketOnlineUsers> activeUsers = <SocketOnlineUsers>[].obs;
  RxSet<MessageHistory> messages = <MessageHistory>{}.obs;
  RxSet<MessageHistory> supportMessages = <MessageHistory>{}.obs;
  RxSet<TypingModel> typingUsers = <TypingModel>{}.obs;
  Rx<ChatRoomModel> singleChatRoom = ChatRoomModel().obs;
  Rx<ChatRoomModel> oldChatroom = ChatRoomModel().obs;
  Rx<String> supportId = ''.obs;
  RxBool seenValue = false.obs;
  RxInt unReadCount = 0.obs;
  int currentMessageCount = 0;

  @override
  void onInit() {
    super.onInit();
    _socketUtility = SocketIOUtility.instance;

    _socketUtility.initSocketIO(
      onConnect: _onConnect,
      onMessage: (data) => _log('Received message: $data'),
      onDisconnect: () => _log('Disconnected from socket'),
      onError: (error) => _log('Socket error: $error'),
    );
  }



  SocketStatus? get socketStatus => _socketUtility.socketStatus;

  void sendMessage({
    required String event,
    required dynamic message,
  }) {
    _socketUtility.sendMessage(event: event, message: message);
  }

  void getDataFromSocket({required String event, required Function data}) {
    _socketUtility.on(event, data);
  }

  void connect() {
    _socketUtility.onConnect!();
  }

  void reConnect() {
    _socketUtility.reconnect();
  }

  void disconnect() {
    _socketUtility.closeSocket();
    heartbeatTimer.cancel();
  }

  void getAllChatRooms() {
    getDataFromSocket(
        event: SocketConstants.chatRoomsResponse,
        data: (data) {
          unReadCount.value = 0;
          List<MessageModel> allMessages =
              Get.find<MessageLogic>().allMessagesList;
          print(
              "From socket logic message ________________________________________ $allMessages");
          allMessages.clear();
          List<MessageModel> dateSortedMessages = [];
          if (data["data"] != null) {
            // for (var message in data["data"]) {
            //   MessageModel jsonValue = MessageModel.fromJson(message);
            //   if (jsonValue.otherUser?.userRole != RoleConstants.admin) {
            //     dateSortedMessages.add(jsonValue);
            //     unReadCount += jsonValue.unreadMessagesCount ?? 0;
            //   } else {
            //     supportId.value = jsonValue.otherUser?.id ?? '';
            //     printColored(supportId.value, PrintedColors.cyan);
            //   }
            // }
            for (var message in data["data"]) {
              print("📩 Incoming message data: $message");

              try {
                if (message['lastMessage'] == null) {
                  print(
                      "⚠ Warning: Message with ID ${message['_id']} has no lastMessage. Assigning default.");
                  message['lastMessage'] = {
                    "_id": "",
                    "text": "",
                    "seen": false,
                    "timestamp": "",
                    "senderId": "",
                    "recipientId": ""
                  };
                }

                MessageModel jsonValue = MessageModel.fromJson(message);
                print("✅ Parsed message: ${jsonValue.lastMessage?.text}");

                // 🚀 Now we include ALL messages, including Admins
                dateSortedMessages.add(jsonValue);
                unReadCount += jsonValue.unreadMessagesCount ?? 0;

                // Keeping track of Admins separately (optional)
                if (jsonValue.otherUser?.userRole == RoleConstants.admin) {
                  supportId.value = jsonValue.otherUser?.id ?? '';
                  printColored(
                      "🔹 Admin message included: ${jsonValue.lastMessage?.text}",
                      PrintedColors.green);
                }
              } catch (e) {
                print("❌ Error parsing message: $message, Error: $e");
              }
            }

            dateSortedMessages.sort((a, b) {
              final String? timeStampA = a.lastMessage?.timeStamp;
              final String? timeStampB = b.lastMessage?.timeStamp;
              if (timeStampA == null && timeStampB == null) {
                return 0;
              } else if (timeStampA == null) {
                return 1;
              } else if (timeStampB == null) {
                return -1;
              } else {
                return timeStampB.compareTo(timeStampA);
              }
            });
            allMessages.addAll(dateSortedMessages);
          }
        });
  }

  RxString? newChatroomId;

  void createChatRoomResponse() {
    getDataFromSocket(
        event: SocketConstants.chatRoomCreated,
        data: (data) {
          newChatroomId?.value = '';
          newChatroomId?.value = data["chatRoomId"];
          joinRoom(chatRoomId: data["chatRoomId"]);
        });
  }

  void createChatRoom({required String otherUserId}) {
    sendMessage(event: SocketConstants.createChatRoom, message: {
      "otherUserId": otherUserId,
      "userId": storageBox.read(Storage.userId)
    });
  }

  Future<void> requestSingleChatRoom({required String chatRoomId}) async {
    print("Requesting chat room with ID: $chatRoomId");
    sendMessage(event: SocketConstants.singleChatRoom, message: {
      // "userRole": storageBox.read(Storage.role),
      "chatRoomId": chatRoomId,
      "userId": storageBox.read(Storage.userId)
    });
  }

  void requestMoreSingleChatRoom({required String chatRoomId}) {
    sendMessage(event: SocketConstants.fetchMoreMessages, message: {
      "chatRoomId": chatRoomId,
      "userId": storageBox.read(Storage.userId),
      'currentMessageCount': currentMessageCount
    });
  }

  void getSingleChatRoom() {
    getDataFromSocket(
        event: SocketConstants.chatRoomResponse,
        data: (data) {
          try {
            var newChatRoom = ChatRoomModel.fromJson(data['data']);
            var newMessages = newChatRoom.messageHistory ?? [];

            if (singleChatRoom.value != null &&
                singleChatRoom.value.messageHistory != null) {
              // Append new messages to the existing message history
              List<MessageHistory> a = [
                ...newMessages,
                ...singleChatRoom.value.messageHistory!
              ];
              singleChatRoom.value.messageHistory!.assignAll(a);
              singleChatRoom.refresh();
            } else {
              singleChatRoom.value = newChatRoom;
              singleChatRoom.refresh();
            }
            currentMessageCount += newMessages.length;
          } catch (e) {
            rethrow;
          }
          if (unReadCount.value != 0) {
            requestChatRooms();
          }
        });
  }

  Future<void> sendMessageToChatRoom(
      {required String messageText,
      required String recipientId,
      required String replyTo}) async {
    sendMessage(event: SocketConstants.sendMessage, message: {
      "text": messageText,
      "recipientId": recipientId,
      "senderId": storageBox.read(Storage.userId),
      "files": [],
      "replyTo": replyTo.isEmpty ? null : replyTo,
    });
  }

  void getMessage() {
    _getMessage(event: SocketConstants.getMessage);
  }

  void getMessageOutSide() {
    _getMessage(event: SocketConstants.getMessageOutSide);
  }

  void _getMessage({required String event}) {
    getDataFromSocket(
        event: event,
        data: (data) async {
          MessageHistory message = MessageHistory.fromJson(data);
          bool isDuplicate = messages.any((msg) => msg.id == message.id);

          if (!isDuplicate) {
            await requestChatRooms();
            if (seenValue.value == true) {
              message.seen = true;
            }
            messages.add(message);
            if (Platform.isIOS) {
              if (message.senderId?.id != storageBox.read(Storage.userId)) {
                LocalNotificationService.localNotificationService
                    .showSimpleNotification(
                  id: messageHistory.indexWhere((e) => e.id == message.id),
                  title: message.senderId?.fullName ?? '',
                  body: message.text ?? '',
                );
              }
            }
          }
        });
  }

  Future<void> deleteMessage({required String messageId}) async {
    sendMessage(event: SocketConstants.deleteMessage, message: {
      "messageId": messageId,
      "userId": storageBox.read(Storage.userId)
    });
  }

  Future<void> joinRoom({required String chatRoomId}) async {
    sendMessage(event: SocketConstants.joinRoom, message: {
      "chatRoomId": chatRoomId,
      "userId": storageBox.read(Storage.userId),
    });
  }

  void messageRead() {
    getDataFromSocket(
        event: SocketConstants.messageRead,
        data: (data) async {
          seenValue.value = true;
        });
  }

  void joinedRoom() {
    getDataFromSocket(
        event: SocketConstants.joinedRoom,
        data: (data) async {
          seenValue.value = true;
          for (MessageHistory i in messages) {
            i.seen = true;
          }
        });
  }

  void leftRoom() {
    getDataFromSocket(
        event: SocketConstants.leftRoom,
        data: (data) async {
          seenValue.value = false;
        });
  }

  Future<void> leaveRoom({required String chatRoomId}) async {
    seenValue.value = false;
    sendMessage(event: SocketConstants.leaveRoom, message: {
      "chatRoomId": chatRoomId,
      "userId": storageBox.read(Storage.userId)
    });
  }

  Future<void> deleteChatRoom({required String chatRoomId}) async {
    sendMessage(event: SocketConstants.deleteChatRoom, message: {
      "chatRoomId": chatRoomId,
      "userId": storageBox.read(Storage.userId)
    });
  }

  void deleteChatRoomResponse() {
    getDataFromSocket(
        event: SocketConstants.deleteChatRoomResponse,
        data: (data) async {
          requestChatRooms();
        });
  }

  void chatRoomDeleted() {
    getDataFromSocket(
        event: SocketConstants.chatRoomDeleted, data: (data) async {});
  }

  void startHeartbeat() {
    void heartBeat() {
      sendMessage(
          event: SocketConstants.heartbeat,
          message: storageBox.read(Storage.userId));
    }

    heartBeat();
    heartbeatTimer = Timer.periodic(const Duration(seconds: 20), (timer) {
      heartBeat();
    });
  }

  void _initializeSocketListeners() {
    getOnlineUsers();
    getMessage();
    getMessageOutSide();
    requestChatRooms();
    getAllChatRooms();
    getUserTyping();
    getSingleChatRoom();
    getSupportAllMessages();
    getUploadedUrls();
    deleteMessageResponse();
    deleteMessageNotification();
    messageRead();
    deleteChatRoomResponse();
    chatRoomDeleted();
    joinedRoom();
    leftRoom();
    createChatRoomResponse();
  }
}

and here is my message_chat_logic:


class MessageChatLogic extends GetxController {
  final MessageChatState state = MessageChatState();

  // final MessagesRepository repository = Get.put(MessagesRepository());
  final DataBaseHelper database = Get.put(DataBaseHelper.instance);
  final SocketLogic socket = Get.find<SocketLogic>();
  TextEditingController textEditingController = TextEditingController();
  RxList<File> files = <File>[].obs;

  String optionsViewId = 'optionsViewId';
  var text = '';
  final isFocus = false.obs;
  final isLoading = false.obs;
  bool isShowPotions = false;

  Rx<MessageModel> messageModel = MessageModel().obs;
  RxSet<MessageHistory> messageHistory = <MessageHistory>{}.obs;
  RxString name = ''.obs;
  RxString recipientId = ''.obs;
  String senderId = storageBox.read(Storage.userId);
  Rx<MessageHistory>? replyMessage = MessageHistory().obs;

  @override
  void onInit() {
    listenToChanges();
    try {
      if (Get.arguments["messageModel"] != null) {
        messageModel.value = Get.arguments["messageModel"];
        name.value = messageModel.value.otherUser?.fullName ?? 'Name';
        recipientId.value = messageModel.value.otherUser?.id ?? '';
        startJoinRoomTimer();
        // getChatRoom();
      } else {
        name.value = Get.arguments["name"];
        recipientId.value = Get.arguments["recipientId"];
        socket.createChatRoom(
            otherUserId: Get.arguments["recipientId"] ?? recipientId.value);
        if (Get.arguments["chatRoomId"] != null) {
          if (socket.newChatroomId?.value.isNotEmpty ?? false) {
            // getChatRoom(socket.newChatroomId.value);
          }
        }
      }
    } catch (e) {
      rethrow;
    }

    super.onInit();
  }

  int countLastFourMessagesSentByCurrentUser() {
    int messageCount = 0;
    List<MessageHistory> uniqueMessages = [];
    Set<String> seenIds = {};

    for (MessageHistory message in messageHistory) {
      if (!seenIds.contains(message.id)) {
        uniqueMessages.add(message);
        seenIds.add(message.id ?? '');
      }
    }

    for (var message in uniqueMessages.toList().sublist(
        messageHistory.length > 4
            ? messageHistory.length - 4
            : uniqueMessages.length)) {
      if (message.senderId?.id == storageBox.read(Storage.userId)) {
        messageCount++;
      }
    }

    return messageCount;
  }

  void startJoinRoomTimer() {
    socket.joinRoom(chatRoomId: messageModel.value.id ?? '');
    // print("timer");
    // });
  }

  void endJoinRoomTimer() {
    socket.leaveRoom(chatRoomId: messageModel.value.id ?? '');

    // joinRoomTimer?.cancel();
  }

  void listenToChanges() {
    isLoading.value = true;

    socket.messages.listen((_) async {
      try {
        List<MessageHistory> messagesToRemove = [];

        for (MessageHistory message in socket.messages) {
          if (message.senderId != null && message.recipientId != null) {
            if ((message.recipientId == senderId &&
                    message.senderId?.id == recipientId.value) ||
                (message.senderId?.id == senderId &&
                    message.recipientId == recipientId.value)) {
              // Match the pending message based on its unique ID
              MessageHistory? pendingMessage = socket
                  .singleChatRoom.value.messageHistory
                  ?.firstWhereOrNull((e) {
                return e.text == message.text &&
                    e.status == MessageStatus.pending;
              });

              // Remove the pending message if it exists
              if (pendingMessage != null) {
                socket.singleChatRoom.value.messageHistory
                    ?.remove(pendingMessage);
                // pendingMessage.status = MessageStatus.sent;
                // socket.singleChatRoom.value.messageHistory?.add(pendingMessage);
              }

              // Add the new message received from the socket
              socket.singleChatRoom.value.messageHistory?.add(message);
              socket.singleChatRoom.refresh();
            }

            if (socket.singleChatRoom.value != null) {
              var chatRoomMessages =
                  socket.singleChatRoom.value.messageHistory ?? [];
              if (!chatRoomMessages.any((msg) => msg.id == message.id)) {
                chatRoomMessages.add(message);
                socket.singleChatRoom.value.messageHistory = chatRoomMessages;
                socket.singleChatRoom.refresh();
              }
            }

            isLoading.value = false;
            update();
          }
        }

        // Safely remove pending messages after iteration
        if (messagesToRemove.isNotEmpty) {
          socket.singleChatRoom.value.messageHistory?.removeWhere((e) {
            database.deleteMessageById(e.id ?? '');
            return messagesToRemove.contains(e);
          });
          socket.singleChatRoom.refresh();
        }
      } catch (e) {
        rethrow;
      }
    });

    socket.singleChatRoom.listen((_) {
      try {
        messageHistory.clear();
        List<MessageHistory> messagesToRemove = [];

        for (MessageHistory message
            in socket.singleChatRoom.value.messageHistory ?? []) {
          if (message.chatRoom ==
              (messageModel.value.id ?? Get.arguments['chatRoomId'])) {
            MessageHistory? pendingMessage = socket
                .singleChatRoom.value.messageHistory
                ?.firstWhereOrNull((e) =>
                    e.text == message.text &&
                    e.status == MessageStatus.pending);

            if (pendingMessage != null) {
              // sendPendingMessage(pendingMessage);
              messagesToRemove.add(pendingMessage);
            }

            messageHistory.add(message);
          }
        }

        // Safely remove pending messages after iteration
        // if (messagesToRemove.isNotEmpty) {
        //   socket.singleChatRoom.value.messageHistory?.removeWhere((e) {
        //     database.deleteMessageById(e.id ?? '');
        //     return messagesToRemove.contains(e);
        //   });
        //   socket.singleChatRoom.refresh();
        // }

        isLoading.value = false;
        update();
      } catch (e) {
        rethrow;
      }
    });
  }

  // void getChatRoom([String? chatRoomId]) async {
  //   isLoading.value = true;
  //   print("Loading started");
  //   try {
  //     print("Before requestSingleChatRoom");
  //     await socket.requestSingleChatRoom(
  //         chatRoomId: chatRoomId ??
  //             (messageModel.value.id ?? Get.arguments['chatRoomId']));
  //     print("Chat room request completed");
  //   } catch (e) {
  //     print("Error fetching chat room: $e");
  //   } finally {
  //     if (isLoading.value) {
  //       print("Setting loading to false");
  //       isLoading.value = false;
  //     }
  //   }
  // }

  Future<FormzSubmissionStatus> getMoreChatRoom() async {
    socket.requestMoreSingleChatRoom(
        chatRoomId: messageModel.value.id ?? Get.arguments['chatRoomId']);
    return FormzSubmissionStatus.success;
  }

  void sendTyping() async {
    await socket.sendTypingStatus(chatRoomId: messageModel.value.id ?? '');
  }

  String parseTimestamp(DateTime timestamp) {
    DateTime dateTime = timestamp;
    String formattedTime = DateFormat.Hm().format(dateTime.toLocal());
    return formattedTime;
  }

  void getFromDatabase() async {
    // Fetch all messages from the database
    List<MessageHistory> list = await database.getAllMessages();

    // Sort the list by the timestamp field in ascending order
    list.sort((a, b) => a.timestamp!.compareTo(b.timestamp!));

    // Add the sorted messages to messageHistory
    socket.singleChatRoom.value.messageHistory?.addAll(list);
  }

  void sendPendingMessage(MessageHistory pendingMessage) async {
    try {
      await socket.sendMessageToChatRoom(
        messageText: pendingMessage.text ?? '',
        recipientId: pendingMessage.recipientId ?? '',
        replyTo: pendingMessage.replyMessage?.id ?? '',
      );
      // newMessage.status = MessageStatus.sent;
    } catch (e) {
      rethrow;
    }
  }

  void sendMessage() async {
    text = text.trim();
    List<FileUploadModel> localFileUrls = [];
    for (File i in files) {
      localFileUrls.add(FileUploadModel(
          url: i.path,
          name: i.path,
          type: "image",
          originalName: '',
          size: i.lengthSync()));
    }

    final MessageHistory newMessage = MessageHistory(
        id: UniqueKey().toString(),
        text: text,
        senderId: SenderId(id: storageBox.read(Storage.userId)),
        recipientId: recipientId.value,
        replyTo: replyMessage?.value.id ?? '',
        status: MessageStatus.pending,
        fileUrls: localFileUrls,
        deleted: false,
        seen: false,
        timestamp: DateTime.now());
    // database.insertMessageHistory(newMessage);
    // getFromDatabase();

    socket.singleChatRoom.value.messageHistory?.add(newMessage);
    socket.singleChatRoom.refresh();
    textEditingController.clear();

    if (files.isNotEmpty) {
      await socket.uploadFiles(
        files: files,
        senderId: senderId,
        recipientId: recipientId.value,
        chatroomId: messageModel.value.id ?? Get.arguments['chatRoomId'],
        text: text,
        replyTo: replyMessage?.value.id ?? '',
        onProgress: (progress) {
          List<FileUploadModel>? fileUrls;
          newMessage.uploadProgress = progress;
          print("File upload progress$progress");
          messageHistory.refresh();
        },
      );
      files.clear();
    } else if (text.isNotEmpty) {
      try {
        await socket.sendMessageToChatRoom(
          messageText: text.trim(),
          recipientId: recipientId.value,
          replyTo: replyMessage?.value.id ?? '',
        );
        // newMessage.status = MessageStatus.sent;
      } catch (e) {
        newMessage.status = MessageStatus.error;
      } finally {
        messageHistory.refresh();
        text = '';
        replyMessage?.value = MessageHistory();
      }
    }
  }

  String formatBytes(int bytes) {
    if (bytes < 0) {
      throw ArgumentError("Value of bytes cannot be negative");
    }
    if (bytes < 1024) {
      return "$bytes B";
    } else if (bytes < 1048576) {
      double kb = bytes / 1024;
      return "${kb.toStringAsFixed(2)} KB";
    } else {
      double mb = bytes / 1048576;
      return "${mb.toStringAsFixed(2)} MB";
    }
  }

  void sendVoice() {}

  void takePhoto() {
    ImagePickTool.takePhoto(callBlock: (xFile) {
      File file = File(xFile.path);
      files.add(file);
    });
  }

  void pickVideo() {
    ImagePickTool.pickVideo(callBlock: (xFile) {
      File file = File(xFile.path);
      files.add(file);
    });
  }

  void getImage() {
    ImagePickTool.pickMultiImage(callBlock: (xFiles) {
      if (xFiles is List<XFile>) {
        List<File> fileList = xFiles.map((xFile) => File(xFile.path)).toList();
        files.addAll(fileList);
      } else {
        print(
            'Error: Expected a List<XFile>, but received ${xFiles.runtimeType}');
      }
    });
  }

  void pickAllFile() {
    ImagePickTool.pickAllFile(successCallBack: (xFiles) {
      if (xFiles is List<XFile>) {
        List<File> fileList = xFiles.map((xFile) => File(xFile.path)).toList();
        files.addAll(fileList);
      } else if (xFiles is File) {
        files.add(xFiles);
      } else {
        printColored(
            'Error: Expected a List<XFile>, but received ${xFiles.runtimeType}',
            PrintedColors.red);
      }
    });
  }

  @override
  void onClose() {
    textEditingController.dispose();
    super.onClose();
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions