Flutter多Widget共享单个StreamProvider 避免Firestore重复查询
解决方案
核心问题原因
你现在的代码每次调用streamBetDataBetweenDates都会生成独立的Stream实例,就算查询参数完全一致,也会触发两次独立的Firestore查询。之前把StreamProvider包裹在ListView外层报错,大概率是没有统一管理Stream的生命周期,或者没有处理用户为空的边界情况。
具体修改方案
1. 统一管理Stream实例
在_DailyPageState中存储复用的Bet数据流,只有当选中日期变化时才重新生成流,避免每次build重复创建流:
class _DailyPageState extends State<DailyPage> { final FirebaseAuth auth = FirebaseAuth.instance; final DateTime now = DateTime.now(); final days = getWeekDayList; int _currentWeekDayNameAndNum = getCurrentWeekDayInt(); final db = FirestoreService(); // 新增:存储当前用户和共享的投注数据流 User? _user; Stream<List<BetData>>? _betStream; @override void initState() { super.initState(); _user = auth.currentUser; // 初始化首次查询流 _refreshBetStream(); } // 新增:根据选中日期更新查询流 void _refreshBetStream() { if (_user == null) return; int selectedDay = int.parse(days[_currentWeekDayNameAndNum]['day']); DateTime startDate = DateTime(now.year, now.month, selectedDay, 0, 0); DateTime endDate = DateTime(now.year, now.month, selectedDay, 23, 59); _betStream = db.streamBetDataBetweenDates(_user!, startDate, endDate); } @override Widget build(BuildContext context) { // 处理用户未登录的边界情况 if (_user == null || _betStream == null) { return const Scaffold(body: Center(child: CircularProgressIndicator())); } return Scaffold( backgroundColor: white.withOpacity(1), // 把StreamProvider移到ListView外层,所有子组件都可以共享这个流的数据 body: StreamProvider<List<BetData>>.value( value: _betStream!, initialData: const [], child: ListView( children: [ // 原来的日期选择组件代码完全保留,不需要修改 Column( children: [ Container( margin: const EdgeInsets.all(10), decoration: BoxDecoration(color: white, boxShadow: [ BoxShadow( color: grey.withOpacity(0.5), spreadRadius: 4, blurRadius: 5, ), ]), child: Padding( padding: const EdgeInsets.only( top: 10, right: 10, left: 10, bottom: 20), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: const [ Text( "Daily Transactions", style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: black), ), ], ), const SizedBox( height: 25, ), Row( crossAxisAlignment: CrossAxisAlignment.start, children: List.generate(days.length, (index) { return GestureDetector( onTap: () { setState(() { _currentWeekDayNameAndNum = index; }); // 切换日期后刷新流 _refreshBetStream(); }, child: SizedBox( width: (MediaQuery.of(context).size.width - 40) / 7, child: Column( children: [ Text( days[index]['label'], style: const TextStyle(fontSize: 10), ), const SizedBox( height: 10, ), Container( width: 30, height: 30, decoration: BoxDecoration( color: _currentWeekDayNameAndNum == index ? primary : Colors.transparent, shape: BoxShape.circle, border: Border.all( color: _currentWeekDayNameAndNum == index ? primary : black .withOpacity(0.1))), child: Center( child: Text( days[index]['day'], style: TextStyle( fontSize: 10, fontWeight: FontWeight.w600, color: _currentWeekDayNameAndNum == index ? white : black), ), ), ) ], ), ), ); })) ], ), ), ), ], ), // 直接使用组件,不需要再单独套StreamProvider const ProfitDisplay(), const SizedBox( height: 10, ), const BetListDisplay() ], ), ), ); } }
2. 后续ProfitDisplay接入额外流的处理
你可以直接在ProfitDisplay内部用StreamBuilder监听额外的流,也可以在父层用MultiProvider添加其他的StreamProvider,不会影响当前共享的BetData流的使用,原有读取BetData的逻辑不需要修改。
验证效果
你可以观察streamBetDataBetweenDates中的print日志,切换日期时只会打印一次,说明只会触发一次Firestore查询,两个组件共享同一份数据。
内容的提问来源于stack exchange,提问作者Trax
相关产品推荐
相关产品推荐

