socket.io-client-dart icon indicating copy to clipboard operation
socket.io-client-dart copied to clipboard

Chatroom not cleaning the history/ Duplicated message on Flutter

Open CaptainJack007 opened this issue 7 months ago • 0 comments

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

CaptainJack007 avatar Mar 11 '25 12:03 CaptainJack007