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

Flutter中StreamBuilder关联数据时Widget不刷新问题咨询

问题解决:收支卡片不刷新的修复方案

问题根源

你当前用全局变量存储totalincome和totalexpense,但Flutter的UI不会因为全局变量的更新自动重建——只有当StatefulWidget的状态变量通过setState更新,或者依赖的Stream/Listenable发生变化时,UI才会刷新。虽然StreamBuilder能正常更新交易列表,但全局变量的修改无法触发顶部卡片的重建。


方案1:用StatefulWidget状态变量管理收支数据

将收支数据改为页面State类的状态变量,在StreamBuilder中计算完成后调用setState更新,触发UI重建。

修改步骤:

  1. 在页面的State类中定义状态变量:
class _ExpenseManagerScreenState extends State<ExpenseManagerScreen> {
  double totalIncome = 0.0;
  double totalExpense = 0.0;
  
  // ...其他代码
}
  1. 修改顶部卡片组件,使用状态变量:
Widget headerSummary(Size size) {
  return Container(
    height: size.height * 0.15,
    decoration: BoxDecoration(
      color: Colors.blue,
      borderRadius: BorderRadius.only(
        bottomRight: Radius.circular(30),
        bottomLeft: Radius.circular(30),
      ),
    ),
    child: Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Expanded(
          child: SummaryCard(
            color: Colors.green,
            amount: totalIncome.toStringAsFixed(2),
            icondata: Icons.arrow_upward,
            title: 'Income',
          ),
        ),
        Expanded(
          child: SummaryCard(
            color: Colors.red,
            amount: totalExpense.toStringAsFixed(2),
            icondata: Icons.arrow_downward,
            title: 'Expense',
          ),
        ),
      ],
    ),
  );
}
  1. 更新StreamBuilder逻辑,计算收支后调用setState:
Widget showTransactions(Size size) {
  return Container(
    height: size.height * .65,
    child: StreamBuilder(
      stream: FirebaseFirestore.instance
          .collection('users')
          .doc(widget.loggeduser.userid)
          .collection('expenses')
          .orderBy("date", descending: true)
          .snapshots(),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.active) {
          if (snapshot.hasData) {
            QuerySnapshot querySnapshot = snapshot.data as QuerySnapshot;
            if (querySnapshot.docs.length > 0) {
              List<Map<String, dynamic>> transactionList = querySnapshot.docs
                  .map((doc) => doc.data() as Map<String, dynamic>)
                  .toList();

              // 计算收支
              double calculatedExpense = transactionList
                  .where((element) => element['isexpense'] == true)
                  .fold(0, (sum, item) => sum + item['amount']);
              double calculatedIncome = transactionList
                  .where((element) => element['isexpense'] == false)
                  .fold(0, (sum, item) => sum + item['amount']);

              // 更新状态,触发顶部卡片刷新
              setState(() {
                totalExpense = calculatedExpense;
                totalIncome = calculatedIncome;
              });

              return ListView.builder(
                padding: EdgeInsets.symmetric(vertical: 10),
                itemCount: transactionList.length,
                itemBuilder: (context, index) {
                  final trans = TransactionModel.fromjson(transactionList[index]);
                  return TransactionCard(
                    amount: trans.amount.toStringAsFixed(2),
                    datetime: trans.date.toString(),
                    paymentby: trans.paymentmode,
                    category: trans.category.title,
                    categoryicon: trans.category.iconurl,
                    isexpense: trans.isexpense,
                  );
                },
              );
            } else {
              // 无交易时重置收支
              setState(() {
                totalExpense = 0;
                totalIncome = 0;
              });
              return Center(child: Text('No Transaction Found...'));
            }
          } else if (snapshot.hasError) {
            return Text('Error occurred');
          } else {
            return Text('Empty');
          }
        } else {
          return Center(child: CircularProgressIndicator());
        }
      },
    ),
  );
}

方案2:用同一个StreamBuilder包裹整个页面

将顶部卡片和交易列表都放在同一个StreamBuilder中,当Firebase数据流更新时,整个页面会重建,直接使用最新计算的收支数据,无需单独维护状态变量。

修改示例:

@override
Widget build(BuildContext context) {
  final size = MediaQuery.of(context).size;
  return Scaffold(
    body: StreamBuilder(
      stream: FirebaseFirestore.instance
          .collection('users')
          .doc(widget.loggeduser.userid)
          .collection('expenses')
          .orderBy("date", descending: true)
          .snapshots(),
      builder: (context, snapshot) {
        double totalIncome = 0;
        double totalExpense = 0;
        List<Map<String, dynamic>> transactionList = [];

        // 处理数据流
        if (snapshot.connectionState == ConnectionState.active) {
          if (snapshot.hasData) {
            QuerySnapshot querySnapshot = snapshot.data as QuerySnapshot;
            if (querySnapshot.docs.isNotEmpty) {
              transactionList = querySnapshot.docs
                  .map((doc) => doc.data() as Map<String, dynamic>)
                  .toList();
              // 计算收支
              totalExpense = transactionList
                  .where((e) => e['isexpense'] == true)
                  .fold(0, (sum, item) => sum + item['amount']);
              totalIncome = transactionList
                  .where((e) => e['isexpense'] == false)
                  .fold(0, (sum, item) => sum + item['amount']);
            }
          }
        }

        return Column(
          children: [
            // 传入计算后的收支数据
            headerSummary(size, totalIncome, totalExpense),
            // 传入交易列表
            showTransactions(size, transactionList),
          ],
        );
      },
    ),
  );
}

// 修改顶部卡片为接收参数
Widget headerSummary(Size size, double totalIncome, double totalExpense) {
  return Container(
    height: size.height * 0.15,
    decoration: BoxDecoration(
      color: Colors.blue,
      borderRadius: BorderRadius.only(
        bottomRight: Radius.circular(30),
        bottomLeft: Radius.circular(30),
      ),
    ),
    child: Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Expanded(
          child: SummaryCard(
            color: Colors.green,
            amount: totalIncome.toStringAsFixed(2),
            icondata: Icons.arrow_upward,
            title: 'Income',
          ),
        ),
        Expanded(
          child: SummaryCard(
            color: Colors.red,
            amount: totalExpense.toStringAsFixed(2),
            icondata: Icons.arrow_downward,
            title: 'Expense',
          ),
        ),
      ],
    ),
  );
}

// 修改交易列表为接收参数
Widget showTransactions(Size size, List<Map<String, dynamic>> transactionList) {
  return Container(
    height: size.height * .65,
    child: transactionList.isNotEmpty
        ? ListView.builder(
            padding: EdgeInsets.symmetric(vertical: 10),
            itemCount: transactionList.length,
            itemBuilder: (context, index) {
              final trans = TransactionModel.fromjson(transactionList[index]);
              return TransactionCard(
                amount: trans.amount.toStringAsFixed(2),
                datetime: trans.date.toString(),
                paymentby: trans.paymentmode,
                category: trans.category.title,
                categoryicon: trans.category.iconurl,
                isexpense: trans.isexpense,
              );
            },
          )
        : Center(child: Text('No Transaction Found...')),
  );
}

方案对比

  • 方案1适合小型页面,逻辑分散但直观,依赖StatefulWidget的状态管理。
  • 方案2逻辑更集中,避免维护额外状态变量,适合结构简单的页面。

内容的提问来源于stack exchange,提问作者Irfan Ganatra

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 07:01:13