Flutter中通过WebSocket实现点击按钮刷新位置详情UI的方案
Dart/Flutter 实现共享WebSocket连接的位置详情更新功能
实现步骤
1. 全局WebSocket连接管理(单例模式)
维护唯一的WebSocket连接,统一处理连接建立、消息收发、异常重连,避免多页面重复创建连接消耗资源。
2. 定义位置数据模型
标准化WebSocket消息的序列化/反序列化格式,确保前后端数据交互一致。
3. 位置列表页
展示7个预加载的位置项,点击条目携带位置ID跳转至详情页。
4. 位置详情页
通过全局WebSocket发送指定位置的更新请求,监听对应ID的返回消息并刷新UI。
5. 异常处理
处理WebSocket断开、重连及消息解析错误,保障功能稳定性。
代码实现
1. WebSocket单例管理类
import 'dart:async'; import 'dart:convert'; import 'package:web_socket_channel/web_socket_channel.dart'; class WebSocketManager { // 单例实例 static final WebSocketManager _instance = WebSocketManager._internal(); factory WebSocketManager() => _instance; WebSocketManager._internal(); WebSocketChannel? _channel; final StreamController<Map<String, dynamic>> _messageController = StreamController.broadcast(); Stream<Map<String, dynamic>> get messageStream => _messageController.stream; // 建立WebSocket连接 Future<void> connect(String url) async { try { _channel = WebSocketChannel.connect(Uri.parse(url)); _channel?.stream.listen( (message) { try { final data = jsonDecode(message) as Map<String, dynamic>; _messageController.add(data); } catch (e) { print('消息解析失败: $e'); } }, onError: (error) { print('WebSocket错误: $error'); _reconnect(url); }, onDone: () { print('WebSocket连接断开'); _reconnect(url); }, ); } catch (e) { print('连接服务器失败: $e'); await Future.delayed(const Duration(seconds: 3)); await connect(url); } } // 发送消息到服务器 void sendMessage(Map<String, dynamic> message) { if (_channel?.closeCode == null) { _channel?.sink.add(jsonEncode(message)); } else { print('WebSocket未连接,无法发送请求'); } } // 自动重连逻辑 Future<void> _reconnect(String url) async { await Future.delayed(const Duration(seconds: 3)); await connect(url); } // 关闭连接(仅在App退出时调用) void disconnect() { _channel?.sink.close(); _messageController.close(); } }
2. 位置数据模型
class LocationData { final String id; final String name; final double latitude; final double longitude; final String updateTime; LocationData({ required this.id, required this.name, required this.latitude, required this.longitude, required this.updateTime, }); // 从JSON解析数据 factory LocationData.fromJson(Map<String, dynamic> json) { return LocationData( id: json['id'], name: json['name'], latitude: json['latitude'].toDouble(), longitude: json['longitude'].toDouble(), updateTime: json['updateTime'], ); } // 转为JSON格式(发送请求时使用) Map<String, dynamic> toJson() { return { 'id': id, 'name': name, 'latitude': latitude, 'longitude': longitude, 'updateTime': updateTime, }; } }
3. 位置列表页
import 'package:flutter/material.dart'; class LocationListPage extends StatelessWidget { // 模拟7个初始位置数据 final List<LocationData> locations = [ LocationData(id: 'loc1', name: '位置1', latitude: 39.9042, longitude: 116.4074, updateTime: '2024-05-20 10:00'), LocationData(id: 'loc2', name: '位置2', latitude: 31.2304, longitude: 121.4737, updateTime: '2024-05-20 10:01'), LocationData(id: 'loc3', name: '位置3', latitude: 23.1291, longitude: 113.2644, updateTime: '2024-05-20 10:02'), LocationData(id: 'loc4', name: '位置4', latitude: 30.2741, longitude: 120.1551, updateTime: '2024-05-20 10:03'), LocationData(id: 'loc5', name: '位置5', latitude: 30.6638, longitude: 104.0668, updateTime: '2024-05-20 10:04'), LocationData(id: 'loc6', name: '位置6', latitude: 22.5431, longitude: 114.0579, updateTime: '2024-05-20 10:05'), LocationData(id: 'loc7', name: '位置7', latitude: 36.6769, longitude: 117.0378, updateTime: '2024-05-20 10:06'), ]; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('位置列表')), body: ListView.builder( itemCount: locations.length, itemBuilder: (context, index) { final location = locations[index]; return ListTile( title: Text(location.name), subtitle: Text('最后更新: ${location.updateTime}'), onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => LocationDetailPage(locationId: location.id, initialData: location), ), ); }, ); }, ), ); } }
4. 位置详情页
import 'package:flutter/material.dart'; class LocationDetailPage extends StatefulWidget { final String locationId; final LocationData initialData; const LocationDetailPage({ super.key, required this.locationId, required this.initialData, }); @override State<LocationDetailPage> createState() => _LocationDetailPageState(); } class _LocationDetailPageState extends State<LocationDetailPage> { late LocationData _currentData; final WebSocketManager _wsManager = WebSocketManager(); @override void initState() { super.initState(); _currentData = widget.initialData; // 监听当前位置的更新消息 _wsManager.messageStream.listen((message) { if (message['id'] == widget.locationId) { setState(() { _currentData = LocationData.fromJson(message); }); } }); } // 发送更新请求 void _requestLatestData() { _wsManager.sendMessage({ 'action': 'refresh_location', 'location_id': widget.locationId, }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(_currentData.name)), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('位置ID: ${_currentData.id}'), const SizedBox(height: 8), Text('纬度: ${_currentData.latitude.toStringAsFixed(4)}'), const SizedBox(height: 8), Text('经度: ${_currentData.longitude.toStringAsFixed(4)}'), const SizedBox(height: 8), Text('最新更新时间: ${_currentData.updateTime}'), const SizedBox(height: 24), Center( child: ElevatedButton( onPressed: _requestLatestData, child: const Text('获取最新位置数据'), ), ), ], ), ), ); } @override void dispose() { // 全局WebSocket连接不在这里关闭,避免影响其他页面 super.dispose(); } }
5. App初始化WebSocket连接
在main.dart中启动时建立连接:
import 'package:flutter/material.dart'; void main() async { // 初始化全局WebSocket连接 await WebSocketManager().connect('ws://your-websocket-server-url'); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: '位置更新Demo', home: LocationListPage(), ); } }
关键注意事项
- 单例生命周期:在App退出时调用
WebSocketManager().disconnect(),可通过WidgetsBindingObserver监听App状态实现。 - 消息过滤:详情页仅处理对应位置ID的消息,避免无关消息触发UI刷新。
- 依赖配置:在
pubspec.yaml中添加依赖:
dependencies: flutter: sdk: flutter web_socket_channel: ^2.4.0
内容的提问来源于stack exchange,提问作者Jason_dd
相关产品推荐
相关产品推荐

