使用Flutter Provider与fl_chart时遇setState/build期间调用错误求助
问题原因分析
- 构建过程中触发状态更新:
showingSections()在组件build()方法中被调用,而你在这个方法里调用了Provider.of<RecProvider>(context, listen: false).getFilteredList(),如果该方法内部调用了notifyListeners()(Provider更新状态的标准方式),就会在组件构建过程中触发状态更新,导致setState() or markNeedsBuild() called during build错误——Flutter禁止在组件构建期间修改状态,否则会引发重复构建的循环冲突。 - 数组越界风险:你用
List.generate(24, (i) {...})循环,但gouvr列表只有6个元素,当i≥6时访问gouvr[i]会直接抛出数组越界异常。
解决方案
1. 重构Provider逻辑,提前计算所有省份的统计数据
不要在build阶段反复调用getFilteredList,而是在Provider内部一次性计算好所有省份的统计结果,存储为一个Map,build时直接读取即可。
修改RecProvider:
class RecProvider extends ChangeNotifier { List<Reclamation> reclamationList = []; Map<String, int> provinceCountMap = {}; // 存储每个省份的诉求数量 // 获取数据后自动计算统计 Future<void> getPosts() async { // 保留原有的数据获取逻辑 reclamationList = await fetchReclamationData(); // 计算每个省份的诉求数量 _calculateProvinceCounts(); notifyListeners(); } void _calculateProvinceCounts() { provinceCountMap.clear(); // 遍历所有诉求统计省份数量 for (var rec in reclamationList) { String province = rec.province; // 假设诉求模型中有province字段对应gouvr元素 provinceCountMap[province] = (provinceCountMap[province] ?? 0) + 1; } // 确保gouvr中的省份都有记录,无数据的设为0 for (var province in gouvr) { provinceCountMap.putIfAbsent(province, () => 0); } } }
2. 修改组件代码,避免在build中修改状态
移除showingSections()里的getFilteredList调用,直接从Provider读取提前计算好的统计数据:
class PieChart2State extends State<PieChartSample2> { @override void initState() { super.initState(); final provider = Provider.of<RecProvider>(context, listen: false); if (provider.reclamationList.isEmpty) { provider.getPosts(); } } int touchedIndex = -1; @override Widget build(BuildContext context) { final provider = Provider.of<RecProvider>(context); return AspectRatio( aspectRatio: 1.3, child: Row( children: <Widget>[ const SizedBox(height: 18), Expanded( child: AspectRatio( aspectRatio: 1, child: PieChart( PieChartData( pieTouchData: PieTouchData( touchCallback: (FlTouchEvent event, pieTouchResponse) { setState(() { if (!event.isInterestedForInteractions || pieTouchResponse == null || pieTouchResponse.touchedSection == null) { touchedIndex = -1; return; } touchedIndex = pieTouchResponse.touchedSection!.touchedSectionIndex; }); }, ), borderData: FlBorderData(show: false), sectionsSpace: 0, centerSpaceRadius: 40, sections: showingSections(provider), ), ), ), ), ], ), ); } List<PieChartSectionData> showingSections(RecProvider provider) { final random = Random(); final total = provider.reclamationList.length; // 遍历gouvr生成图表区域,避免数组越界 return gouvr.asMap().entries.map((entry) { final i = entry.key; final province = entry.value; final count = provider.provinceCountMap[province] ?? 0; if (count == 0) return null; // 无数据的省份不显示 final isTouched = i == touchedIndex; final fontSize = isTouched ? 25.0 : 16.0; final radius = isTouched ? 60.0 : 50.0; const shadows = [Shadow(color: Colors.black, blurRadius: 2)]; return PieChartSectionData( color: Color.fromARGB( 255, random.nextInt(256), random.nextInt(256), random.nextInt(256), ), value: total == 0 ? 0 : (100 * count) / total, title: province, radius: radius, titleStyle: TextStyle( fontSize: fontSize, fontWeight: FontWeight.bold, color: Colors.black, shadows: shadows, ), ); }).whereType<PieChartSectionData>().toList(); } }
3. 关键改进点
- 将统计逻辑从build阶段移到Provider内部,在数据加载完成后一次性计算,避免重复计算和状态更新冲突。
- 使用
gouvr.asMap()循环,彻底解决数组越界问题。 - 在build中仅读取Provider状态,不修改状态,符合Flutter状态管理规范。
- 处理了总诉求数为0的情况,避免除以0的异常。
内容的提问来源于stack exchange,提问作者Eya Rais
相关产品推荐
相关产品推荐

