Flutter中StreamBuilder关联数据时Widget不刷新问题咨询
问题解决:收支卡片不刷新的修复方案
问题根源
你当前用全局变量存储totalincome和totalexpense,但Flutter的UI不会因为全局变量的更新自动重建——只有当StatefulWidget的状态变量通过setState更新,或者依赖的Stream/Listenable发生变化时,UI才会刷新。虽然StreamBuilder能正常更新交易列表,但全局变量的修改无法触发顶部卡片的重建。
方案1:用StatefulWidget状态变量管理收支数据
将收支数据改为页面State类的状态变量,在StreamBuilder中计算完成后调用setState更新,触发UI重建。
修改步骤:
- 在页面的State类中定义状态变量:
class _ExpenseManagerScreenState extends State<ExpenseManagerScreen> { double totalIncome = 0.0; double totalExpense = 0.0; // ...其他代码 }
- 修改顶部卡片组件,使用状态变量:
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', ), ), ], ), ); }
- 更新
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
相关产品推荐
相关产品推荐

