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

Flutter中如何在StreamBuilder构建后更新AppBar标题的totalValue?

解决Flutter中StreamBuilder内调用setState导致的构建错误

错误原因

你遇到的setState() or markNeedsBuild() called during build错误,是因为在**StreamBuilder的builder函数(属于Widget构建流程环节)**中调用了setState。此时Flutter框架正处于构建Widget的阶段,不允许在这个过程中标记Widget需要重新构建,否则会打乱正常的构建流程。

解决方案

方案一:延迟执行setState到构建完成后

使用SchedulerBinding.addPostFrameCallback,将setState的执行延迟到当前Widget构建周期完成之后:

修改StreamBuilder中的计算逻辑部分:

// 替换原来直接赋值或注释的setState代码
SchedulerBinding.instance.addPostFrameCallback((_) {
  setState(() {
    totalValue = filteredDocuments.fold(
        0.0, (num sum, doc) => sum + (doc['cost'] ?? 0.0));
  });
});

方案二:用ValueNotifier实现无setState的状态更新(更优雅)

通过ValueNotifier和ValueListenableBuilder将数值计算与UI展示解耦,避免依赖setState触发全局重建:

  1. 在State类中定义ValueNotifier并处理资源释放:
class _FilterChipExampleState extends State<FilterChipExample> {
  // ... 其他原有代码
  ValueNotifier<num> totalValueNotifier = ValueNotifier(0); // 新增

  @override
  void dispose() {
    totalValueNotifier.dispose(); // 页面销毁时释放资源
    super.dispose();
  }
  // ...
}
  1. 修改StreamBuilder中的数值计算逻辑:
// 替换原来的totalValue赋值代码
totalValueNotifier.value = filteredDocuments.fold(
    0.0, (num sum, doc) => sum + (doc['cost'] ?? 0.0));
  1. 修改AppBar的title为ValueListenableBuilder:
appBar: AppBar(
  title: ValueListenableBuilder<num>(
    valueListenable: totalValueNotifier,
    builder: (context, value, child) {
      return Text('$value');
    },
  ),
),

修改后的完整代码(方案二示例)

import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:calc_tag/services/get_items_for_month.dart';

class FilterChipExample extends StatefulWidget {
  final int year;
  final int month;
  FilterChipExample({required this.year, required this.month});
  @override
  _FilterChipExampleState createState() => _FilterChipExampleState();
}

class _FilterChipExampleState extends State<FilterChipExample> {
  final List<String> categories = [
    'Costanera Norte',
    'Vespucio Norte',
    'Vespucio Sur',
    'Autopista Central',
  ];
  ValueNotifier<num> totalValueNotifier = ValueNotifier(0);
  String titulo = '';

  List<String> selectedCategories = [];

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: ValueListenableBuilder<num>(
          valueListenable: totalValueNotifier,
          builder: (context, value, child) {
            return Text('$value');
          },
        ),
      ),
      body: Column(
        children: [
          Container(
            padding: const EdgeInsets.all(8.0),
            margin: const EdgeInsets.all(8.0),
            child: Wrap(
              children: categories.map((category) {
                return FilterChip(
                    selectedColor: Colors.grey,
                    showCheckmark: false,
                    visualDensity:
                        const VisualDensity(vertical: -4, horizontal: -4),
                    selected: selectedCategories.contains(category),
                    label: Container(
                      margin: const EdgeInsets.all(0.0),
                      child: Text(category),
                    ),
                    onSelected: (selected) {
                      setState(() {
                        if (selected) {
                          selectedCategories.add(category);
                        } else {
                          selectedCategories.remove(category);
                        }
                      });
                    });
              }).toList(),
            ),
          ),
          Expanded(
            child: StreamBuilder<QuerySnapshot>(
              stream: getItemsForMonth(widget.year, widget.month),
              builder: (context, snapshot) {
                if (snapshot.hasError) {
                  return Center(child: Text('Error: ${snapshot.error}'));
                }
                if (!snapshot.hasData) {
                  return const Center(child: CircularProgressIndicator());
                }

                final documents = snapshot.data!.docs;
                final filteredDocuments = documents.where((element) {
                  return selectedCategories.contains(element['contract']);
                }).toList();

                totalValueNotifier.value = filteredDocuments.fold(
                    0.0, (num sum, doc) => sum + (doc['cost'] ?? 0.0));

                return ListView.builder(
                  itemCount: filteredDocuments.length,
                  itemBuilder: (context, index) {
                    return ListTile(
                      title: Text(
                          '${filteredDocuments[index]['contract']} ${(filteredDocuments[index]['created'] as Timestamp).toDate()}'),
                      subtitle: Row(
                        mainAxisAlignment: MainAxisAlignment.start,
                        children: [
                          Text(
                              '\$ ${filteredDocuments[index]['cost'].toString()}'),
                          const SizedBox(
                            width: 30,
                          ),
                          Text(
                            ' ${filteredDocuments[index]['category'].toString()} ',
                            style: const TextStyle(
                                color: Colors.white,
                                fontWeight: FontWeight.bold,
                                fontSize: 10),
                          ),
                        ],
                      ),
                    );
                  },
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

内容的提问来源于stack exchange,提问作者Jaime Albornoz Soto

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 10:34:57