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

Flutter macOS端build()触发但界面未更新,仅热重载后生效?

问题:Flutter桌面应用动态更新列表无界面变化,仅热重载生效

我有一个每秒生成连续整数的异步流:

Stream<int> f() async* {
  for (int i = 0; i < 100; ++i) {
    await Future.delayed(Duration(seconds: 1));
    yield i;
  }
}

定义状态变量List<Widget> l = [];,希望通过该流每秒向列表中添加一个Card组件:

@override
void initState() {
    super.initState();
    f().listen((d) {
      setState(
          () => this.l.add(Card(child: ListTile(title: Text(d.toString())))));
    });
}

对应的build()方法如下:

@override
Widget build(BuildContext context) {
  print('build(): ${this.l.length}');
  return ListView(children: this.l);
}

通过flutter run -d macos运行macOS桌面应用时,尽管build()每秒都会被调用,且this.l持续更新,但界面完全没有变化,控制台输出如下:

flutter: build(): 0
flutter: build(): 1
flutter: build(): 2
flutter: build(): 3
flutter: build(): 4
flutter: build(): 5
...

只有按下r键执行热重载后,界面才会更新。

复现步骤:

  • 执行flutter run -d macos启动应用;
  • 尽管build()每秒触发,界面仍保持全黑状态;
  • 按下r键执行热重载,界面更新。

完整代码:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
          brightness: Brightness.dark,
          primaryColor: Colors.blueGrey,
          useMaterial3: true),
      home: Scaffold(body: W()),
      debugShowCheckedModeBanner: false,
    );
  }
}

class W extends StatefulWidget {
  const W({
    super.key,
  });

  @override
  State<W> createState() => _WState();
}

class _WState extends State<W> {
  List<Widget> l = [];

  @override
  void initState() {
    super.initState();

    Stream<int> f() async* {
      for (int i = 0; i < 100; ++i) {
        await Future.delayed(Duration(seconds: 1));
        yield i;
      }
    }

    f().listen((d) {
      setState(
          () => this.l.add(Card(child: ListTile(title: Text(d.toString())))));
    });
  }

  @override
  Widget build(BuildContext context) {
    print('build(): ${this.l.length}');
    return ListView(children: this.l);
  }
}

原因分析

问题核心在于直接修改原有列表对象的操作:this.l.add(...)只是在原列表基础上添加元素,列表本身的内存引用没有发生变化。

Flutter的setState机制通过对比状态的引用变化判断是否需要更新UI。对于List这类集合类型,Flutter会检查引用是否相等——因为始终操作同一个列表对象,setState认为状态未发生实质性变更,因此不会触发有效的UI重建。尽管build()被调用,但ListView接收的是同一个列表引用,不会重新渲染子组件列表。

热重载时Flutter会重新构建整个组件树,此时ListView会重新读取列表当前内容,界面才会更新。


解决方法

需要在setState中创建新的列表对象,让状态引用发生变化,才能让Flutter识别到更新:

方法一:复制原有元素生成新列表

setState(() {
  this.l = List.from(this.l)
    ..add(Card(child: ListTile(title: Text(d.toString()))));
});

方法二:使用扩展运算符生成新列表

setState(() {
  this.l = [...this.l, Card(child: ListTile(title: Text(d.toString())))];
});

方法三:使用StreamBuilder(推荐的响应式写法)

无需手动维护状态列表,直接通过StreamBuilder监听流并构建UI:

class _WState extends State<W> {
  final List<int> _dataList = [];
  late StreamSubscription _subscription;

  Stream<int> f() async* {
    for (int i = 0; i < 100; ++i) {
      await Future.delayed(Duration(seconds: 1));
      yield i;
    }
  }

  @override
  void initState() {
    super.initState();
    _subscription = f().listen((d) {
      setState(() {
        _dataList.add(d);
        // 创建新列表触发UI更新
        _dataList = List.from(_dataList);
      });
    });
  }

  @override
  void dispose() {
    _subscription.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    print('build(): ${_dataList.length}');
    return ListView(
      children: _dataList.map((d) => Card(child: ListTile(title: Text(d.toString())))).toList(),
    );
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 05:43:21