You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,提问作者Миша Тирский

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 20:52:07