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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 20:35:32