Flutter聊天应用发送消息闪烁消失问题求助
解决Flutter聊天消息闪烁问题
问题根源在于定时拉取消息时直接清空并全量替换本地消息列表,导致刚发送的本地临时消息被清除,1秒后才从数据库拉回,产生闪烁。以下是几种可行的解决方法:
方法1:增量更新消息列表(推荐)
不再清空整个列表,而是对比拉取到的消息和本地现有消息,只添加新增内容,同时替换本地临时消息为数据库返回的真实消息。
步骤1:给ChatMessage添加临时标识
修改ChatMessage类,增加字段标记临时消息:
class ChatMessage { // 原有字段保留... bool isTemp; // 新增:标记是否为本地临时消息 ChatMessage({ // 原有参数保留... this.isTemp = false, }); factory ChatMessage.fromJson(Map<String, dynamic> json) { return ChatMessage( // 原有字段赋值保留... isTemp: false, ); } }
步骤2:发送消息时创建临时消息
修改_handleSubmitted方法,给临时消息标记isTemp: true,用负数作为临时ID避免和真实ID冲突:
_handleSubmitted(String text) async { try { // 创建临时消息,用时间戳生成唯一临时ID ChatMessage newMessage = ChatMessage( user_id: 0, id: -DateTime.now().millisecondsSinceEpoch, message: text, timestamp: DateTime.now().toString(), senderUserId: 0, isTemp: true, ); setState(() { _textController.clear(); _messages.insert(0, newMessage); }); // 发送消息到数据库的逻辑不变 SharedPreferences prefs = await SharedPreferences.getInstance(); int? userIds = prefs.getInt('user_id'); var request = http.MultipartRequest('POST', Uri.parse(API.SendMessageChat)); if (widget.user_idWidget != null && widget.senderUserIdWidget != null) { request.fields['user_id'] = widget.user_idWidget.toString(); request.fields['senderUserId'] = widget.senderUserIdWidget.toString(); } request.fields['id'] = userIds.toString(); request.fields['message'] = text; request.fields['timestamp'] = newMessage.timestamp; var response = await request.send(); if (response.statusCode != 200) { // 发送失败:移除临时消息并提示 setState(() { _messages.remove(newMessage); }); } } catch (e) { print("Error: $e"); // 异常处理:移除对应临时消息 setState(() { _messages.removeWhere((msg) => msg.isTemp && msg.message == text); }); } }
步骤3:修改定时拉取逻辑为增量更新
修改fetchMessagesPeriodically方法,不再清空列表,而是合并消息并替换临时消息:
void fetchMessagesPeriodically() { const Duration delay = Duration(seconds: 1); Timer.periodic(delay, (Timer timer) async { try { var response = await http.post( Uri.parse(API.GetAllMessageChat), body: { 'senderUserId': widget.senderUserIdWidget.toString(), 'user_id': widget.user_idWidget.toString(), }, ); if (response.statusCode == 200) { List<dynamic> data = json.decode(response.body); List<ChatMessage> fetchedChats = data.map((item) => ChatMessage.fromJson(item)).toList().reversed.toList(); setState(() { // 收集本地非临时消息的ID集合 Set<int> existingRealIds = _messages.where((msg) => !msg.isTemp).map((msg) => msg.id).toSet(); // 筛选拉取到的新消息(本地没有的真实消息) List<ChatMessage> newFetchedMessages = fetchedChats.where((msg) => !existingRealIds.contains(msg.id)).toList(); // 替换本地临时消息为数据库返回的真实消息 for (var fetchedMsg in fetchedChats) { int tempIndex = _messages.indexWhere((msg) => msg.isTemp && msg.message == fetchedMsg.message && msg.timestamp == fetchedMsg.timestamp ); if (tempIndex != -1) { _messages[tempIndex] = fetchedMsg; } } // 添加新消息到列表开头 _messages.insertAll(0, newFetchedMessages); // 去重避免重复添加 _messages = _messages.toSet().toList(); }); } else { print('Failed to load messages'); } } catch (e) { print('Error: $e'); } }); }
方法2:发送消息时临时暂停拉取
在发送消息过程中取消定时器,发送完成后重启,避免拉取操作覆盖临时消息:
// 新增成员变量保存定时器 Timer? _fetchTimer; @override void initState() { super.initState(); _fetchTimer = Timer.periodic(const Duration(seconds: 1), _fetchMessages); } // 把拉取逻辑抽成单独方法 void _fetchMessages(Timer timer) async { try { var response = await http.post( Uri.parse(API.GetAllMessageChat), body: { 'senderUserId': widget.senderUserIdWidget.toString(), 'user_id': widget.user_idWidget.toString(), }, ); if (response.statusCode == 200) { List<dynamic> data = json.decode(response.body); List<ChatMessage> chats = data.map((item) => ChatMessage.fromJson(item)).toList(); setState(() { _messages.clear(); _messages.addAll(chats.reversed); }); } else { print('Failed to load messages'); } } catch (e) { print('Error: $e'); } } _handleSubmitted(String text) async { // 暂停拉取 _fetchTimer?.cancel(); // 创建临时消息并插入列表的逻辑不变... try { // 发送消息到数据库的逻辑不变... // 发送成功后重启拉取 _fetchTimer = Timer.periodic(const Duration(seconds: 1), _fetchMessages); } catch (e) { print("Error: $e"); // 失败也重启拉取 _fetchTimer = Timer.periodic(const Duration(seconds: 1), _fetchMessages); } } @override void dispose() { _fetchTimer?.cancel(); super.dispose(); }
这种方法实现简单,但如果发送消息耗时超过1秒,仍可能出现闪烁,优先推荐方法1的增量更新方案。
内容的提问来源于stack exchange,提问作者Миша Тирский
相关产品推荐
相关产品推荐

