如何在Flutter的ChatBox组件中结合Socket.IO实现消息即时展示
问题解决:Flutter Socket.IO聊天消息即时展示与渲染错误修复
问题背景
使用Flutter结合Socket.IO开发聊天应用时,ChatBox组件存在两个核心问题:
- 收到的新消息无法实时更新到UI
- 消息渲染逻辑错误,展示内容不符合预期
修复方案
针对上述问题,对代码做以下关键修改:
1. 正确解析Socket消息数据
在chat_list_message事件回调中,恢复并完善消息解析逻辑,确保从Socket推送的消息列表中正确提取每条消息并添加到本地消息数组。
2. 触发UI即时更新
在收到新消息后调用setState,通知Flutter框架重新构建UI,实现消息的实时展示。
3. 修正消息渲染逻辑
调整ListView中的消息渲染代码,使用消息自身的username而非组件传入的widget.username,确保每条消息显示对应的发送者名称。
完整修正代码
class ChatBox extends StatefulWidget { const ChatBox({super.key, required this.liveID, required this.username}); final int liveID; final String username; @override State<ChatBox> createState() => _ChatBoxState(); } class _ChatBoxState extends State<ChatBox> { final TextEditingController _messageController = TextEditingController(); bool _showEmojiPicker = false; late IO.Socket socket; int? currentUserID; String? currentUsername; List<ChatMessage> _messages = []; final StreamController<String> _streamController = StreamController<String>(); Stream<String> get messagesStream => _streamController.stream; void _onEmojiSelected(Emoji emoji) { _messageController ..text += emoji.emoji ..selection = TextSelection.fromPosition( TextPosition(offset: _messageController.text.length), ); } void _onBackspacePressed() { _messageController ..text = _messageController.text.characters.skipLast(1).toString() ..selection = TextSelection.fromPosition( TextPosition(offset: _messageController.text.length), ); } @override void initState() { super.initState(); _loadCurrentUserID(); _initializeSocket(); } @override void dispose() { _messageController.dispose(); _streamController.close(); socket.disconnect(); super.dispose(); } Future<void> _loadCurrentUserID() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); setState(() { currentUserID = prefs.getInt('loginUserId'); currentUsername = prefs.getString('username'); }); } void _initializeSocket() { socket = IO.io('https://socket.example.com', <String, dynamic>{ 'transports': ['websocket'], 'autoConnect': false, }); socket.connect(); socket.onConnect((_) { debugPrint('connected to websocket'); }); socket.onDisconnect((_) { debugPrint('disconnected from websocket'); }); socket.on('chat_list_message', (data) { debugPrint('Received message: $data'); setState(() { if (data is Map && data['message'] is List && data['message'].isNotEmpty) { for (var messageObj in data['message']) { if (messageObj is Map && messageObj.containsKey('message')) { String messageContent = messageObj['message']; _messages.add(ChatMessage( userID: messageObj['userID'], liveID: messageObj['liveID'], message: messageContent, username: messageObj['username'], )); debugPrint('Received Message: $messageContent'); } else { debugPrint('Unexpected message object: $messageObj'); } } } }); }); socket.onError((error) { debugPrint('Socket error: $error'); }); } void _sendMessage() { String message = _messageController.text.trim(); if (message.isNotEmpty) { final newMessage = { 'userID': currentUserID, 'liveID': widget.liveID, 'message': message, 'username': currentUsername ?? 'Unknown', 'time': DateTime.now().toIso8601String(), }; setState(() { _messages.add(ChatMessage.fromJson(newMessage)); }); socket.emit('add_chat_message', newMessage); _messageController.clear(); } } @override Widget build(BuildContext context) { return BlocListener<ChatDataLiveBloc, ChatDataLiveState>( listener: (context, state) { if (state is ChatDataLiveSuccess) { setState(() { _messages = state.allChatDataLives ?? []; }); } else if (state is ChatDataLiveFail) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(state.message)), ); } }, child: SizedBox( height: 150, child: Column( children: [ Padding( padding: const EdgeInsets.all(8.0), child: Row( children: [ Expanded( child: Divider( color: Theme.of(context).primaryColor, height: 1, thickness: 1.5, ), ), const SizedBox(width: 8.0), Text( 'New Message', overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.bodySmall, maxLines: 1, ), const SizedBox(width: 8.0), Expanded( child: Divider( color: Theme.of(context).primaryColor, height: 1, thickness: 1.5, ), ), ], ), ), Expanded( child: ListView.builder( itemCount: _messages.length, itemBuilder: (context, index) { final message = _messages[index]; return Row( children: [ const SizedBox(width: 20), Text( '${message.username} : ', overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.bodyMedium, maxLines: 1, ), const SizedBox(width: 10), Text( message.message ?? '', overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.bodySmall, maxLines: 1, ), ], ); }, ), ), Container( margin: const EdgeInsets.only(left: 12.0, top: 12), child: Row( children: [ Expanded( child: TextField( controller: _messageController, decoration: const InputDecoration( hintText: 'Type your message...', border: OutlineInputBorder(), ), ), ), const SizedBox(width: 8.0), IconButton( icon: const Icon(Icons.emoji_emotions), onPressed: () { setState(() { _showEmojiPicker = !_showEmojiPicker; }); }, ), IconButton( icon: const Icon(Icons.send), onPressed: () { _sendMessage(); }, ), ], ), ), Offstage( offstage: !_showEmojiPicker, child: SizedBox( height: 250, child: EmojiPicker( onEmojiSelected: (category, emoji) { _onEmojiSelected(emoji); }, onBackspacePressed: _onBackspacePressed, config: const Config( height: 200, checkPlatformCompatibility: true, emojiViewConfig: EmojiViewConfig( emojiSizeMax: 32, ), swapCategoryAndBottomBar: false, ), ), ), ), ], ), ), ); } }
内容的提问来源于stack exchange,提问作者cadeting
相关产品推荐
相关产品推荐

