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

Flutter中如何在添加数据时自动刷新ListView.builder

需求与问题

我希望向apartmentUpdate列表添加元素时,ListView.builder能自动刷新并更新内容。以下是我的当前代码结构,同时欢迎任何代码重构的建议,提前感谢。

class CheckInUpdateLogCard extends Stateful Widget {
  const CheckInUpdateLogCard({super.key});

  @override
  State<CheckInUpdateLogCard> createState() => _CheckInUpdateLogCardState();
}

class _CheckInUpdateLogCardState extends State<CheckInUpdateLogCard> {
  final ScrollController _controller = ScrollController();

  late StreamController<List<String>> _activityTextController;
  late Stream<List<String>> activityTextStream;
  final List<String> apartmentUpdate = [
    'Slavador',
    'Cusco',
    'Helsinki',
    'Suzda',
    'Slavador',
    'Cusco',
    'Helsinki',
    'New',
    'New',
  ];

  // @override
  // void initState() {
  //   super.initState();
  //   activityTextStream = getActivityText();
  // }
  @override
  void initState() {
    super.initState();
    _activityTextController = StreamController<List<String>>();
    updateActivityText();
  }

  // Stream<List<String>> getActivityText() async* {
  //   while (true) {
  //     await Future.delayed(const Duration(seconds: 5));
  //     _activityTextController.add(apartmentUpdate);
  //   }
  // }

  void updateActivityText() async {
    while (true) {
      await Future.delayed(const Duration(seconds: 5));
      setState(() {
        _activityTextController.add(apartmentUpdate);
      });
    }
  }

  @override
  void dispose() {
    _activityTextController.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 500,
      width: double.infinity,
      padding: const EdgeInsets.all(20),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(20),
        color: AppColors.greyBackgrond,
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          const ApartmentText(
            theText: 'Recent Activities',
            theColor: AppColors.headerColorBlack,
            theSize: 20,
          ),
          kSpacerHeight30,
          StreamBuilder<List<String>>(
              stream: _activityTextController.stream,
              builder: (context, snapshot) {
                if (snapshot.connectionState == ConnectionState.waiting) {
                  return Center(
                      child: CircularProgressIndicator(
                    backgroundColor: AppColors.greyBackgrond,
                    color: AppColors.mainAppColor,
                  ));
                } else if (snapshot.hasData) {
                  final textData = snapshot.data!;
                  WidgetsBinding.instance.addPostFrameCallback((_) {
                    _controller.animateTo(
                      _controller.position.maxScrollExtent,
                      duration: const Duration(milliseconds: 1000),
                      curve: Curves.easeOut,
                    );
                  });
                  return Expanded(
                    child: ListView.builder(
                        controller: _controller,
                        physics: const BouncingScrollPhysics(),
                        itemCount: textData.length,
                        itemBuilder: ((context, index) {
                          return Column(
                            children: [
                              ActivitiesText(theText: textData[index]),
                            ],
                          );
                        })),
                  );
                } else {
                  return const Text('No Data');
                }
              }),
          kSpacerHeight20,
        ],
      ),
    );
  }
}

class ActivitiesText extends StatelessWidget {
  const ActivitiesText({
    Key? key,
    required this.theText,
  }) : super(key: key);
  final String theText;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 40,
      width: double.infinity - 40,
      padding: const EdgeInsets.only(
        top: 15,
        bottom: 5,
        right: 5,
        left: 10,
      ),
      margin: const EdgeInsets.only(right: 40, bottom: 15),
      decoration: const BoxDecoration(
          color: AppColors.mainAppColorLighter,
          borderRadius: BorderRadius.only(
            bottomLeft: Radius.circular(10),
            topRight: Radius.circular(10),
            bottomRight: Radius.circular(10),
          )),
      child: Text(
        '$theText Occupied',
        overflow: TextOverflow.ellipsis,
        textAlign: TextAlign.left,
        style: const TextStyle(
          fontSize: 16,
          color: AppColors.headerColorBlack,
        ),
      ),
    );
  }
}
解决方法与重构建议

一、核心修改:实现列表自动刷新

  1. 将列表改为可变状态变量
    原代码中apartmentUpdate是final不可变列表,无法触发状态更新。改为late List<String>并在initState初始化:

    late List<String> apartmentUpdate;
    
    @override
    void initState() {
      super.initState();
      _activityTextController = StreamController<List<String>>();
      apartmentUpdate = [
        'Slavador',
        'Cusco',
        'Helsinki',
        'Suzda',
        'Slavador',
        'Cusco',
        'Helsinki',
        'New',
        'New',
      ];
      // 初始化时发送一次数据给Stream
      _activityTextController.add(List.from(apartmentUpdate));
      updateActivityText();
    }
    
  2. 优化updateActivityText方法
    原循环只是重复发送相同列表,改为添加新元素后发送列表副本(List是引用类型,必须传副本让Stream识别变化):

    void updateActivityText() async {
      while (true) {
        await Future.delayed(const Duration(seconds: 5));
        // 模拟添加新元素
        apartmentUpdate.add('New Activity ${DateTime.now().second}');
        // 发送列表副本
        _activityTextController.add(List.from(apartmentUpdate));
      }
    }
    
  3. 优化滚动触发逻辑
    避免每次build都滚动到底部,只在列表长度增加时触发:

    // 在State类中添加变量记录之前的列表长度
    int _previousListLength = 0;
    
    // 在StreamBuilder的hasData分支中修改:
    final textData = snapshot.data!;
    if (textData.length > _previousListLength) {
      _previousListLength = textData.length;
      WidgetsBinding.instance.addPostFrameCallback((_) {
        _controller.animateTo(
          _controller.position.maxScrollExtent,
          duration: const Duration(milliseconds: 1000),
          curve: Curves.easeOut,
        );
      });
    }
    

二、关键重构优化

  1. 修复ActivitiesText宽度错误
    double.infinity - 40是无效写法,改用Padding包裹容器来控制宽度:

    class ActivitiesText extends StatelessWidget {
      const ActivitiesText({
        Key? key,
        required this.theText,
      }) : super(key: key);
      final String theText;
    
      @override
      Widget build(BuildContext context) {
        return Padding(
          padding: const EdgeInsets.only(right: 40, bottom: 15),
          child: Container(
            height: 40,
            width: double.infinity,
            padding: const EdgeInsets.only(
              top: 15,
              bottom: 5,
              right: 5,
              left: 10,
            ),
            decoration: const BoxDecoration(
                color: AppColors.mainAppColorLighter,
                borderRadius: BorderRadius.only(
                  bottomLeft: Radius.circular(10),
                  topRight: Radius.circular(10),
                  bottomRight: Radius.circular(10),
                )),
            child: Text(
              '$theText Occupied',
              overflow: TextOverflow.ellipsis,
              textAlign: TextAlign.left,
              style: const TextStyle(
                fontSize: 16,
                color: AppColors.headerColorBlack,
              ),
            ),
          ),
        );
      }
    }
    
  2. 移除冗余的setState包裹
    _activityTextController.add()不需要放在setState内,Stream发送数据本身就能触发StreamBuilder更新:

    void updateActivityText() async {
      while (true) {
        await Future.delayed(const Duration(seconds: 5));
        apartmentUpdate.add('New Activity ${DateTime.now().second}');
        _activityTextController.add(List.from(apartmentUpdate));
      }
    }
    
  3. 可选:用setState替代Stream简化代码
    如果没有跨组件数据传递需求,直接用setState更新更简洁,去掉所有Stream相关代码:

    class _CheckInUpdateLogCardState extends State<CheckInUpdateLogCard> {
      final ScrollController _controller = ScrollController();
      late List<String> apartmentUpdate;
      int _previousListLength = 0;
    
      @override
      void initState() {
        super.initState();
        apartmentUpdate = [
          'Slavador',
          'Cusco',
          'Helsinki',
          'Suzda',
          'Slavador',
          'Cusco',
          'Helsinki',
          'New',
          'New',
        ];
        _previousListLength = apartmentUpdate.length;
        updateActivityText();
      }
    
      void updateActivityText() async {
        while (true) {
          await Future.delayed(const Duration(seconds: 5));
          setState(() {
            apartmentUpdate.add('New Activity ${DateTime.now().second}');
          });
        }
      }
    
      @override
      void didUpdateWidget(covariant CheckInUpdateLogCard oldWidget) {
        super.didUpdateWidget(oldWidget);
        if (apartmentUpdate.length > _previousListLength) {
          _previousListLength = apartmentUpdate.length;
          WidgetsBinding.instance.addPostFrameCallback((_) {
            _controller.animateTo(
              _controller.position.maxScrollExtent,
              duration: const Duration(milliseconds: 1000),
              curve: Curves.easeOut,
            );
          });
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return Container(
          // 保留原有容器样式代码
          child: Column(
            children: [
              const ApartmentText(
                theText: 'Recent Activities',
                theColor: AppColors.headerColorBlack,
                theSize: 20,
              ),
              kSpacerHeight30,
              Expanded(
                child: ListView.builder(
                    controller: _controller,
                    physics: const BouncingScrollPhysics(),
                    itemCount: apartmentUpdate.length,
                    itemBuilder: ((context, index) {
                      return ActivitiesText(theText: apartmentUpdate[index]);
                    })),
              ),
              kSpacerHeight20,
            ],
          ),
        );
      }
    }
    

三、其他细节优化

  • 释放ScrollController资源:在dispose中添加_controller.dispose(),避免内存泄漏。
  • 增强错误处理:在StreamBuilder中添加snapshot.hasError分支,显示错误提示。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 00:44:56