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

Dart中如何在WebSocket数据更新后立即获取数据?

问题描述

我通过WebSocket事件获取数据,但需要在数据到达WebSocket时立即更新页面数据。我尝试过用Stream,但目前只能通过Stream.periodic(Duration(seconds: 2))实现,而且返回的是静态Future——Future没办法订阅更新,只能刷新页面才能拿到新数据。

但用Stream.periodic时,页面里的WebSocket图片会闪烁,我需要图片保持静态,同时数据能即时更新。

请问该怎么修改?能不能结合ValueListenableBuilder和WebSocket来实现?

现有代码
List wanted = [];
static Stream<List?> getWanted() => Stream.periodic(Duration(seconds: 10))
      .asyncMap((event) => fetchWantedFixation());

static Future<List> fetchWantedFixation() async {
  final httpConnectionOptions = HttpConnectionOptions(
      accessTokenFactory: () => SharedPreferenceService().loginWithToken(),
      skipNegotiation: true,
      transport: HttpTransportType.WebSockets);
  final hubConnection = HubConnectionBuilder()
      .withUrl(
        'http://my securelink/',
        options: httpConnectionOptions,
      )
      .build();
  await hubConnection.start();
  String? wantedFixation;
  Future.delayed(const Duration(seconds: 7))
      .then((value) => alarmplayer.StopAlarm());
  hubConnection.on('Wanted', (arguments) {
    HapticFeedback.mediumImpact();
    // SystemSound.play(SystemSoundType.click);
    wanted = arguments as List;
    alarmplayer.Alarm(url: 'assets/wanted.mp3', volume: 0.25);
  });

  hubConnection.onclose(({error}) {
    throw Exception(error);
  });
  print(wanted);
  return wanted;
}
解决方案

你的核心问题是重复创建WebSocket连接(Stream.periodic每10秒就会调用fetchWantedFixation,每次都新建HubConnection),这不仅浪费资源,还会引发UI闪烁。正确的做法是保持单个WebSocket连接,通过事件驱动实时推送数据到UI。

方案1:自定义Stream实现实时推送

不需要Stream.periodic,直接在WebSocket回调里将数据推送到Stream:

class WantedService {
  static final WantedService _instance = WantedService._internal();
  factory WantedService() => _instance;
  WantedService._internal();

  HubConnection? _hubConnection;
  final _controller = StreamController<List>.broadcast();
  List _wanted = [];

  Stream<List> get wantedStream => _controller.stream;

  Future<void> initConnection() async {
    if (_hubConnection?.state == HubConnectionState.Connected) return;

    final httpConnectionOptions = HttpConnectionOptions(
      accessTokenFactory: () => SharedPreferenceService().loginWithToken(),
      skipNegotiation: true,
      transport: HttpTransportType.WebSockets,
    );
    _hubConnection = HubConnectionBuilder()
        .withUrl(
          'http://my securelink/',
          options: httpConnectionOptions,
        )
        .build();

    _hubConnection!.on('Wanted', (arguments) {
      HapticFeedback.mediumImpact();
      _wanted = arguments as List;
      _controller.add(_wanted); // 实时推送新数据到Stream
      alarmplayer.Alarm(url: 'assets/wanted.mp3', volume: 0.25);
      Future.delayed(const Duration(seconds: 7)).then((_) => alarmplayer.StopAlarm());
    });

    _hubConnection!.onclose(({error}) {
      if (error != null) {
        print('连接关闭:$error');
        // 可在此添加重连逻辑
      }
    });

    await _hubConnection!.start();
  }

  void dispose() {
    _controller.close();
    _hubConnection?.stop();
  }
}

使用方式

在页面中用StreamBuilder监听数据:

@override
void initState() {
  super.initState();
  WantedService().initConnection();
}

@override
Widget build(BuildContext context) {
  return StreamBuilder<List>(
    stream: WantedService().wantedStream,
    initialData: [],
    builder: (context, snapshot) {
      // 用snapshot.data渲染UI,数据更新时自动刷新,无闪烁
      return YourImageWidget(data: snapshot.data);
    },
  );
}

@override
void dispose() {
  WantedService().dispose();
  super.dispose();
}

方案2:结合ValueListenableBuilder实现

如果更倾向用ValueNotifier,可采用以下写法:

class WantedService {
  static final WantedService _instance = WantedService._internal();
  factory WantedService() => _instance;
  WantedService._internal();

  HubConnection? _hubConnection;
  final ValueNotifier<List> wantedNotifier = ValueNotifier([]);

  Future<void> initConnection() async {
    if (_hubConnection?.state == HubConnectionState.Connected) return;

    final httpConnectionOptions = HttpConnectionOptions(
      accessTokenFactory: () => SharedPreferenceService().loginWithToken(),
      skipNegotiation: true,
      transport: HttpTransportType.WebSockets,
    );
    _hubConnection = HubConnectionBuilder()
        .withUrl(
          'http://my securelink/',
          options: httpConnectionOptions,
        )
        .build();

    _hubConnection!.on('Wanted', (arguments) {
      HapticFeedback.mediumImpact();
      final newData = arguments as List;
      wantedNotifier.value = newData; // 更新ValueNotifier触发UI刷新
      alarmplayer.Alarm(url: 'assets/wanted.mp3', volume: 0.25);
      Future.delayed(const Duration(seconds: 7)).then((_) => alarmplayer.StopAlarm());
    });

    _hubConnection!.onclose(({error}) {
      if (error != null) {
        print('连接关闭:$error');
        // 可在此添加重连逻辑
      }
    });

    await _hubConnection!.start();
  }

  void dispose() {
    wantedNotifier.dispose();
    _hubConnection?.stop();
  }
}

使用方式

用ValueListenableBuilder监听数据:

@override
void initState() {
  super.initState();
  WantedService().initConnection();
}

@override
Widget build(BuildContext context) {
  return ValueListenableBuilder<List>(
    valueListenable: WantedService().wantedNotifier,
    builder: (context, value, child) {
      return YourImageWidget(data: value);
    },
  );
}

@override
void dispose() {
  WantedService().dispose();
  super.dispose();
}

关键修改点

  • 用单例模式维护单个WebSocket连接,避免重复创建(这是UI闪烁的核心原因)
  • 改用事件驱动:在WebSocket的Wanted回调里直接推送数据到Stream/ValueNotifier,实现即时更新
  • 移除Stream.periodic轮询逻辑,避免无效请求
  • 添加dispose方法,释放资源防止内存泄漏

内容的提问来源于stack exchange,提问作者user16357470

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 21:11:39