Flutter星级评分异常:多次点击后平均值重复累加问题求助
问题描述
在Flutter应用中实现了星级评分系统,不同用户可为物品打分,后续计算所有评分的总和与平均值并展示在UI上。用户点击物品时,评分以DraggableScrollableSheet形式呈现。首次点击按钮时平均值显示正确,但多次点击后,每次点击都会将平均值与之前数值累加——真实平均值为av、点击次数为n时,UI显示的平均值A=av*n。
相关代码:
double sum = 0; Stream<double> getSumRating(String passUID) async* { await FirebaseFirestore.instance .collection("Collection") .doc(docID) .collection("Collection") .get() .then((value) => value.docs.forEach((element) { sum += element.get("Stars"); })); yield sum; } Stream<int> getSumList() async* { var snapshot = await FirebaseFirestore.instance .collection("Collection") .doc(docID) .collection("Collection") .get(); yield snapshot.docs.length; } ratingBar( double rating, double size, ) { List<Widget> startList = []; int realNum = rating.floor(); for (int i = 0; i < 5; i++) { if (i == 0) {} if (i < realNum) { startList.add(Icon( Icons.star, color: Colors.amber, size: size, )); } else { startList.add(Icon( Icons.star, color: Colors.grey, size: size, )); } } return Row( children: startList, ); } Button( onPressed:(){ draggrableSheet(); } ), draggrableSheet() { showModalBottomSheet( isScrollControlled: true, backgroundColor: Colors.transparent, context: context, builder: (context) => DraggableScrollableSheet( initialChildSize: 0.9, minChildSize: 0.2, maxChildSize: 0.9, builder: (context, ScrollController scrollController) { return Container( color: Colors.white, child: SizedBox( width: screenWidth * 0.5, child: StreamBuilder( stream: getSumList(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const CircularProgressIndicator(); } else if (snapshot.hasError) { return Text( "Error"); } else { final sumList = snapshot.data; final sumOfList = sumList!.toDouble(); return Column( children: [ StreamBuilder( stream: getSumRating(passUID), builder: (context, snapshot) { final averageReal = sum / sumOfList; final average = averageReal; if (snapshot.connectionState == ConnectionState.waiting) { return const CircularProgressIndicator(); } else if (snapshot.hasError) { return Text(widget .errorLoadingRequestString); } if (average.isNaN || average.isInfinite) { return Column( children: [ ratingBar(0, 20), SizedBox( height: screenHeight * 0.005, ), ], ); } else { final averageInt = average.floor(); return Column( children: [ Row( children: [ ratingBar(average, 20), SizedBox( width: screenWidth * 0.05, ), Text( "$averageInt/5", //this is where the number keeps increasing on every click ), ], ), ], ); } }, ), ], ); } }, ), ) ], ); } }, ), ); }), ); }
错误原因
问题出在全局变量sum的累加逻辑:
- 你定义了全局的
double sum = 0;,每次调用getSumRating时,都会在原有sum的基础上累加新获取的评分值,而非重新计算当前所有评分的总和。 - 第一次点击时
sum为初始值0,计算后得到正确总和;第二次点击时sum已保留第一次的总和,再次累加就变成两倍真实总和,以此类推,平均值也随之翻倍。
修复方案
方案1:将sum改为局部变量
把sum的定义移到getSumRating方法内部,每次调用都重置为0:
Stream<double> getSumRating(String passUID) async* { double sum = 0; // 移到方法内,每次调用重新初始化 await FirebaseFirestore.instance .collection("Collection") .doc(docID) .collection("Collection") .get() .then((value) => value.docs.forEach((element) { sum += element.get("Stars"); })); yield sum; }
方案2:用fold直接计算总和(更简洁)
无需额外sum变量,用fold方法直接计算所有评分的总和:
Stream<double> getSumRating(String passUID) async* { final snapshot = await FirebaseFirestore.instance .collection("Collection") .doc(docID) .collection("Collection") .get(); final sum = snapshot.docs.fold(0.0, (total, doc) => total + doc.get("Stars")); yield sum; }
额外优化:合并Stream请求
当前用两个StreamBuilder分别获取评分数量和总和,可合并为一个请求,减少Firestore调用次数:
Stream<Map<String, dynamic>> getRatingStats() async* { final snapshot = await FirebaseFirestore.instance .collection("Collection") .doc(docID) .collection("Collection") .get(); final count = snapshot.docs.length; final sum = snapshot.docs.fold(0.0, (total, doc) => total + doc.get("Stars")); yield {"count": count, "sum": sum}; }
UI中使用单个StreamBuilder:
StreamBuilder( stream: getRatingStats(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const CircularProgressIndicator(); } else if (snapshot.hasError) { return Text("Error"); } else { final stats = snapshot.data!; final count = stats["count"]; final sum = stats["sum"]; final average = count > 0 ? sum / count : 0.0; if (average.isNaN || average.isInfinite) { return Column( children: [ ratingBar(0, 20), SizedBox(height: screenHeight * 0.005), ], ); } else { final averageInt = average.floor(); return Column( children: [ Row( children: [ ratingBar(average, 20), SizedBox(width: screenWidth * 0.05), Text("$averageInt/5"), ], ), ], ); } } }, )
内容的提问来源于stack exchange,提问作者EBENEZER OCANSEY
相关产品推荐
相关产品推荐

