Flutter使用Firestore新增数据后出现数据冗余问题求助
Firestore数据重复问题排查与解决
问题现象
使用Firestore获取数据时,新增一条数据后,新数据无法显示,旧数据反而重复出现;重启APP后数据恢复正常。当前使用最新版Firestore包,采用GetX依赖注入,通过GetxService保持实例持久化。
相关代码
数据获取逻辑
Future<QuerySnapshot<Map<String, dynamic>>> getPubs() async { queryPubs = await pubsDb.get(); if (queryPubs!.docs.isEmpty) { log("[getPubs] Variable queryPubs is empty"); } else { await pubData(queryPubs!.docs).then((value) { if (value.isNotEmpty) { isPub.value = true; log("[getPubs] Variable pubAll is not empty"); } else { log("[getPubs] Variable pubAll is empty"); } }); } return queryPubs!; } Future<List<Map<String, dynamic>>> pubData(List<DocumentSnapshot>? docs) async { for (int i = 0; i < docs!.length; i++) { DbPublisher dbPublisher = DbPublisher.fstoreInfo(docs[i]); final imgUrl = await getImgUrl(dbPublisher.imgName, DataType.PUBLISHER); pubInfo = { "name": dbPublisher.name, "imgURL": imgUrl, "imgName": dbPublisher.imgName, "desc": dbPublisher.desc, "email": dbPublisher.email, "active": dbPublisher.active, "status": dbPublisher.status, "albums": dbPublisher.albums, "songs": dbPublisher.songs, "state": dbPublisher.state, "privilege": dbPublisher.privilege, "payout": dbPublisher.payout, "compensation": dbPublisher.compensation, "started": dbPublisher.started, "due": dbPublisher.due, "license": dbPublisher.license, "duration": dbPublisher.duration, "valid": dbPublisher.valid, }; pubAll!.add(pubInfo); } return pubAll!; }
UI渲染代码
Expanded( child: FutureBuilder<QuerySnapshot<Map<String, dynamic>>>( future: dNd.getPubs(), builder: (context, AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) { if (snapshot.connectionState == ConnectionState.done) { if (snapshot.hasData) { listAlbs = snapshot.data!.docs; if (listAlbs.isNotEmpty) { totalAlbs = listAlbs.length; log("Load $totalAlbs albums from Firestore"); return ListView.builder( padding: EdgeInsets.zero, shrinkWrap: true, physics: const ScrollPhysics(), controller: scrollCtrl, itemCount: listAlbs.length, itemBuilder: (context, index) { return albsFstore(context, dNd.pubAll![index], index); }, ); } else { return const Center(child: FavText("The album data is empty", 15.0)); } } else if (snapshot.hasError) { return const Center(child: FavText("The server is error", 15.0)); } else { return const Center(child: FavText("The data is empty", 15.0)); } } else if (snapshot.connectionState == ConnectionState.waiting) { return AlignPositioned( dx: 0, dy: -(MediaQuery.of(context).size.height / 4), child: Container( width: 128, height: 128, padding: const EdgeInsets.all(8.0), decoration: const BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(16.0)), color: Color.fromARGB(88, 44, 44, 44)), child: const LoadingIndicator(indicatorType: Indicator.ballRotateChase, colors: rainbowColors),),); } return const SizedBox(); }, ), ),
问题原因
pubAll列表未清空重复添加
由于GetxService保持实例持久化,pubAll是全局复用的列表。每次调用getPubs()时,都会从Firestore拉取全量数据,然后在pubData中循环将数据追加到pubAll中,导致旧数据被重复添加。重启APP后pubAll重新初始化,数据恢复正常。FutureBuilder重复触发请求
直接将dNd.getPubs()作为FutureBuilder的future参数,每次UI重建时都会创建新的Future实例,导致getPubs()被多次调用,进一步加剧pubAll的数据重复问题。
解决方案
1. 填充数据前清空pubAll
在pubData方法开头添加清空逻辑,避免旧数据残留:
Future<List<Map<String, dynamic>>> pubData(List<DocumentSnapshot>? docs) async { pubAll!.clear(); // 清空旧数据,避免重复添加 for (int i = 0; i < docs!.length; i++) { // 原有代码逻辑不变 } return pubAll!; }
2. 优化FutureBuilder使用,避免重复请求
不要直接在FutureBuilder中调用getPubs(),而是在initState中初始化Future并复用:
late Future<QuerySnapshot<Map<String, dynamic>>> _pubsFuture; @override void initState() { super.initState(); _pubsFuture = dNd.getPubs(); // 仅初始化一次 } // UI中使用复用的Future Expanded( child: FutureBuilder<QuerySnapshot<Map<String, dynamic>>>( future: _pubsFuture, builder: (context, snapshot) { // 原有builder逻辑不变 }, ), )
3. 改用GetX响应式状态管理(推荐)
利用GetX的响应式列表自动触发UI刷新,替代FutureBuilder,同时避免数据重复:
步骤1:在GetxService中定义响应式列表
final RxList<Map<String, dynamic>> pubAll = <Map<String, dynamic>>[].obs; final RxBool isPub = false.obs;
步骤2:修改pubData方法,替换列表而非追加
Future<void> pubData(List<DocumentSnapshot>? docs) async { final tempList = <Map<String, dynamic>>[]; for (int i = 0; i < docs!.length; i++) { DbPublisher dbPublisher = DbPublisher.fstoreInfo(docs[i]); final imgUrl = await getImgUrl(dbPublisher.imgName, DataType.PUBLISHER); final pubInfo = { // 原有字段不变 }; tempList.add(pubInfo); } pubAll.assignAll(tempList); // 替换整个列表,彻底避免重复 isPub.value = tempList.isNotEmpty; }
步骤3:UI用Obx监听数据变化
Expanded( child: Obx(() { if (dNd.isPub.value) { if (dNd.pubAll.isNotEmpty) { log("Load ${dNd.pubAll.length} albums from Firestore"); return ListView.builder( padding: EdgeInsets.zero, shrinkWrap: true, physics: const ScrollPhysics(), controller: scrollCtrl, itemCount: dNd.pubAll.length, itemBuilder: (context, index) { return albsFstore(context, dNd.pubAll[index], index); }, ); } else { return const Center(child: FavText("The album data is empty", 15.0)); } } else if (dNd.pubAll.isEmpty) { return const Center(child: FavText("The data is empty", 15.0)); } else { return AlignPositioned( dx: 0, dy: -(MediaQuery.of(context).size.height / 4), child: Container( width: 128, height: 128, padding: const EdgeInsets.all(8.0), decoration: const BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(16.0)), color: Color.fromARGB(88, 44, 44, 44) ), child: const LoadingIndicator(indicatorType: Indicator.ballRotateChase, colors: rainbowColors), ), ); } }), )
4. 可选:使用Firestore实时监听
通过snapshots()监听数据变化,自动获取新增数据,无需手动调用getPubs():
// 在GetxService的onInit中监听 @override void onInit() { super.onInit(); pubsDb.snapshots().listen((snapshot) { pubData(snapshot.docs); // 数据变化时自动更新 }); }
内容的提问来源于stack exchange,提问作者Wege
相关产品推荐
相关产品推荐

