如何实现Firestore数据实时更新?Flutter列表视图不刷新问题
如何实现Firestore数据实时更新,让ListView自动刷新?
我用FutureBuilder结合ListView.builder展示餐厅列表,每个列表项调用ApprovedRestaurants组件从Firestore获取数据渲染。但点击按钮更新餐厅的status为Approved或Banned后,列表视图不会自动刷新,必须切换页面才会更新。
上层列表代码:
FutureBuilder( future: getRestaurantsList(), builder: (context, snapshot) { return ListView.builder( itemCount: approveList.length, itemBuilder: (context, index) { return ListTile( title: ApprovedRestaurants(documentId: approveList[index] as String, Index: index ,), ); }); })
ApprovedRestaurants组件代码:
FutureBuilder<DocumentSnapshot>( future: restaurants.doc(documentId).get(), builder: ((BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot){ if(snapshot.connectionState == ConnectionState.done){ Map<String, dynamic> data = snapshot.data!.data() as Map<String,dynamic>; return Card( child: Container( padding: EdgeInsets.all(MediaQuery.of(context).size.height / 150), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( children: [ Container( width: MediaQuery.of(context).size.width / 1.17, height: MediaQuery.of(context).size.height / 8, decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), color: Colors.white38, image: DecorationImage( fit: BoxFit.cover, image: AssetImage(AppImages.foodImg))), ), SizedBox(height: MediaQuery.of(context).size.height / 200,), Text(data['restaurantName'], style: TextStyle(fontWeight: FontWeight.w700,fontSize: 12),), SizedBox(height: MediaQuery.of(context).size.height / 120,), Text(data['phoneNumber'],style: TextStyle(fontSize: 11),), SizedBox(height: MediaQuery.of(context).size.height / 250,), Text(data['email'],style: TextStyle(fontSize: 11),), SizedBox(height: MediaQuery.of(context).size.height / 250,), Text(data['address'],style: TextStyle(fontSize: 11),), Row( children: [ IconButton(onPressed: (){ FirebaseFirestore.instance.collection("Restaurants").doc(documentId).update({'status' : 'Approved'}); }, icon: Icon(Icons.check, color: Colors.green,)), IconButton(onPressed: (){ FirebaseFirestore.instance.collection("Restaurants").doc(documentId).update({'status' : 'Banned'}); }, icon: Icon(Icons.close, color: Colors.red,)), ], ), ], ), ], ) ), );; } return Container( child: AlertDialog( //loading dialogue here ), ); }));
解决方案:用StreamBuilder替代FutureBuilder实现实时监听
FutureBuilder只会在Future完成时触发一次UI重建,而StreamBuilder能持续监听Firestore的数据流变化,数据更新时自动刷新UI。
1. 上层列表改用StreamBuilder监听集合快照
替换原来的FutureBuilder,直接监听餐厅集合的实时快照,无需依赖全局的approveList变量:
StreamBuilder<QuerySnapshot>( stream: FirebaseFirestore.instance.collection("Restaurants").snapshots(), builder: (context, snapshot) { // 加载状态处理 if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } // 无数据处理 if (!snapshot.hasData || snapshot.data!.docs.isEmpty) { return const Center(child: Text("暂无餐厅数据")); } final restaurantDocs = snapshot.data!.docs; return ListView.builder( itemCount: restaurantDocs.length, itemBuilder: (context, index) { final docId = restaurantDocs[index].id; // 直接返回组件,无需ListTile包裹(组件本身是Card) return ApprovedRestaurants(documentId: docId, index: index); }, ); }, )
2. ApprovedRestaurants组件改用StreamBuilder监听单个文档快照
把原来一次性获取文档的get()换成实时监听的snapshots(),确保单个餐厅状态更新时组件自动刷新:
StreamBuilder<DocumentSnapshot>( stream: FirebaseFirestore.instance.collection("Restaurants").doc(documentId).snapshots(), builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) { // 加载状态处理 if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } // 文档不存在处理 if (!snapshot.hasData || !snapshot.data!.exists) { return const Text("该餐厅已不存在"); } Map<String, dynamic> data = snapshot.data!.data() as Map<String, dynamic>; return Card( child: Container( padding: EdgeInsets.all(MediaQuery.of(context).size.height / 150), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( children: [ Container( width: MediaQuery.of(context).size.width / 1.17, height: MediaQuery.of(context).size.height / 8, decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), color: Colors.white38, image: const DecorationImage( fit: BoxFit.cover, image: AssetImage(AppImages.foodImg), ), ), ), SizedBox(height: MediaQuery.of(context).size.height / 200), Text( data['restaurantName'], style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 12), ), SizedBox(height: MediaQuery.of(context).size.height / 120), Text(data['phoneNumber'], style: const TextStyle(fontSize: 11)), SizedBox(height: MediaQuery.of(context).size.height / 250), Text(data['email'], style: const TextStyle(fontSize: 11)), SizedBox(height: MediaQuery.of(context).size.height / 250), Text(data['address'], style: const TextStyle(fontSize: 11)), Row( children: [ IconButton( onPressed: () { FirebaseFirestore.instance .collection("Restaurants") .doc(documentId) .update({'status': 'Approved'}); }, icon: const Icon(Icons.check, color: Colors.green), ), IconButton( onPressed: () { FirebaseFirestore.instance .collection("Restaurants") .doc(documentId) .update({'status': 'Banned'}); }, icon: const Icon(Icons.close, color: Colors.red), ), ], ), ], ), ], ), ), ); }, );
关键说明
- 上层监听整个集合的快照:新增、删除餐厅时,列表会自动更新
- 单个组件监听自身文档快照:点击按钮修改
status后,组件会即时刷新 - 移除了全局的
approveList变量,避免手动维护数据导致的状态不同步问题
内容的提问来源于stack exchange,提问作者H. M. Zaheer Asghar
相关产品推荐
相关产品推荐

