Flutter接入Firebase Database时数据重复添加无法更新的修复求助
问题根因
- Firebase Realtime Database的
onValue监听器会在对应节点数据每次变动时触发全量回调,原有代码每次回调都直接向本地列表执行add操作,没有清空旧数据,导致每次数据更新都会重复累加条目 - 未存储每个产品对应的Firebase节点唯一key,后续更新操作无法定位到需要修改的具体节点,只能触发新增逻辑
- 缺少更新数据的交互与提交逻辑,原有浮动按钮点击事件为空,没有对接Firebase的更新接口
- 原有列表空判断逻辑
productNames != []永远返回true,空数据场景下会引发索引异常
调整方案
- 每次监听器触发回调时,先清空本地所有存储产品数据的列表,再添加新拉取的全量数据,避免重复
- 新增
productKeys列表存储每个产品对应的Firebase节点key,更新操作时通过该key定位目标节点 - 给列表卡片增加点击交互,点击后弹出编辑窗口输入新的产品数值,调用Firebase的
update方法提交更新 - 修正列表空判断逻辑,直接使用列表长度判断即可
修正后完整代码
import 'package:flutter/material.dart'; import 'package:firebase_database/firebase_database.dart'; // 请确保你已经导入了FontAwesomeIcons相关依赖 import 'package:font_awesome_flutter/font_awesome_flutter.dart'; // 你原有项目的自定义样式和组件,保持不变 const kBlackTS = TextStyle(color: Colors.black); Widget emptySpaceWidth(BuildContext context, double val) => SizedBox(width: MediaQuery.of(context).size.width * val); Widget emptySpaceHeight(BuildContext context, double val) => SizedBox(height: MediaQuery.of(context).size.height * val); class CustomAppBar { final BuildContext context; final Color colour; final String title; CustomAppBar({required this.context, required this.colour, required this.title}); AppBar get customAppBar => AppBar(backgroundColor: colour, title: Text(title)); } class DetailedCountingScreen extends StatefulWidget { final String countDate; final String standName; const DetailedCountingScreen( {required this.countDate, required this.standName}); @override _DetailedCountingScreenState createState() => _DetailedCountingScreenState(); } class _DetailedCountingScreenState extends State<DetailedCountingScreen> { final firebaseRef = FirebaseDatabase.instance.reference(); // 新增存储产品节点key的列表 List<String> productKeys = []; List<String> productNames = []; List<int> soldPieces = []; List<int> countedPieces = []; List<double> productPrices = []; @override void initState() { // 修正:super.initState要放在initState最前面 super.initState(); firebaseRef .child('Sayımlar') .child(widget.standName) .child('${widget.countDate}') .onValue .listen((event) { final snapshot = event.snapshot.value ?? {}; final countedProductsMap = Map<String, dynamic>.from(snapshot); setState(() { // 新增:每次拉取到新数据先清空旧列表,避免重复 productKeys.clear(); productNames.clear(); soldPieces.clear(); countedPieces.clear(); productPrices.clear(); countedProductsMap.forEach((key, value) { // 新增:存储每个产品的节点key productKeys.add(key); productNames.add(value['ürünİsmi']); soldPieces.add(value['satılanMiktar']); countedPieces.add(value['sayılanMiktar']); productPrices.add(value['ürünFiyatı']); }); }); }); } // 新增:更新产品数据的方法 Future<void> _updateProduct(int index, int newSold, int newCounted, double newPrice) async { await firebaseRef .child('Sayımlar') .child(widget.standName) .child('${widget.countDate}') .child(productKeys[index]) .update({ 'satılanMiktar': newSold, 'sayılanMiktar': newCounted, 'ürünFiyatı': newPrice, }); if(mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('产品更新成功'))); } } // 新增:点击卡片弹出编辑窗口的逻辑 void _showEditDialog(int index) { final soldController = TextEditingController(text: soldPieces[index].toString()); final countedController = TextEditingController(text: countedPieces[index].toString()); final priceController = TextEditingController(text: productPrices[index].toString()); showDialog( context: context, builder: (context) => AlertDialog( title: Text('编辑 ${productNames[index]}'), content: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: soldController, keyboardType: TextInputType.number, decoration: const InputDecoration(labelText: '销售数量'), ), TextField( controller: countedController, keyboardType: TextInputType.number, decoration: const InputDecoration(labelText: '盘点数量'), ), TextField( controller: priceController, keyboardType: TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration(labelText: '产品价格'), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('取消'), ), TextButton( onPressed: () { final newSold = int.tryParse(soldController.text) ?? soldPieces[index]; final newCounted = int.tryParse(countedController.text) ?? countedPieces[index]; final newPrice = double.tryParse(priceController.text) ?? productPrices[index]; _updateProduct(index, newSold, newCounted, newPrice); Navigator.pop(context); }, child: const Text('保存'), ), ], ) ); } Widget countsCard({ required int soldPiece, required double productPrice, required int countedPiece, required String productName, required int thePieceBeforeCount, required double totalPrice, // 新增点击回调参数 required VoidCallback onTap, }) { return Card( color: Colors.white, // 新增点击事件 child: InkWell( onTap: onTap, child: ListTile( isThreeLine: true, title: Text( productName.toUpperCase(), style: kBlackTS.copyWith(fontWeight: FontWeight.bold), ), subtitle: Column( children: [ Row( children: [ Text('Sayılan Adet: $countedPiece', style: kBlackTS), emptySpaceWidth(context, 0.04), Text('Satılan Adet: $soldPiece', style: kBlackTS), emptySpaceWidth(context, 0.04), Text('Sayım Öncesi Adet: $thePieceBeforeCount', style: kBlackTS), ], ), emptySpaceHeight(context, 0.015), Row( children: [ Text('Satış Fiyatı: ${productPrice.toStringAsFixed(2)} ₺', style: kBlackTS), emptySpaceWidth(context, 0.04), Text('Toplam Tutar: ${totalPrice.toStringAsFixed(2)} ₺', style: kBlackTS), ], ), ], ), ), ), ); } FloatingActionButton get _invoiceFAB { return FloatingActionButton( backgroundColor: Colors.blue.shade700, child: Icon(FontAwesomeIcons.fileInvoice, color: Colors.white), // 你可以在这里补充生成发票的逻辑 onPressed: () => ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('生成发票功能可在此处扩展'))) ); } @override Widget build(BuildContext context) { CustomAppBar customAppBar = CustomAppBar( context: context, colour: Colors.blue.shade700, title: '${widget.countDate} Tarihli Sayımlar'); return SafeArea( child: Scaffold( backgroundColor: Colors.blue.shade100, appBar: customAppBar.customAppBar, body: Container( child: Column( children: [ Expanded( // 修正:原来的ListView外面包Expanded,避免布局溢出 child: ListView.builder( shrinkWrap: true, // 修正:空判断逻辑,直接用列表长度即可 itemCount: productNames.length, itemBuilder: (context, index) { double total = soldPieces[index] * productPrices[index]; int thePieceBeforeCount = soldPieces[index] + countedPieces[index]; return countsCard( soldPiece: soldPieces[index], productPrice: productPrices[index], countedPiece: countedPieces[index], productName: productNames[index], thePieceBeforeCount: thePieceBeforeCount, totalPrice: total, // 新增传入点击编辑的逻辑 onTap: () => _showEditDialog(index), ); }, ), ), ], ), ), floatingActionButton: _invoiceFAB, ), ); } }
功能验证方式
修改完成后,点击任意产品卡片即可弹出编辑窗口,修改数值后点击保存,Firebase会自动更新对应节点的数据,onValue监听器会自动拉取最新数据刷新列表,不会再出现重复条目的问题。
内容的提问来源于stack exchange,提问作者Yemliha Demir
相关产品推荐
相关产品推荐

