修改Firebase数据时如何更新Riverpod的StreamNotifier状态?
问题:Flutter Riverpod + Firebase实时数据库编辑数据后状态不更新
背景
我正在开发一款Flutter应用,采用Riverpod进行状态管理,Firebase实时数据库作为数据存储方案。以下是我编写的PackStreamNotifier类代码,用于从Firebase获取数据并将状态设置为Pack模型类的流:
class PackStreamNotifier extends StreamNotifier<List<Pack>> { late DatabaseReference _dbRef; PackStreamNotifier() : super() { _dbRef = FirebaseDatabase.instance.ref('packages'); } @override Stream<List<Pack>> build() { return fetchData(); } Stream<List<Pack>> fetchData() async* { List<Pack> packs = []; try { final data = await _dbRef.once().then((value) => value.snapshot); for (var dataNode in data.children) { Map<dynamic, dynamic> map = dataNode.value as Map; map.putIfAbsent('id', () => dataNode.key); //formatIntoPack method formats the map into model class (Pack). packs.add(formatIntoPack(map)); } yield packs; state = AsyncValue.data(packs); } catch (error) { throw Exception('failed to fetch data'); } } /* Editing an individual pack in firebase realtime DB. Doesn't work as intended */ Stream<List<Pack>> editRecord(packId, Map<String, Object> updatedData) { _editPack(packId, updatedData); return fetchData(); } void _editPack(packId, Map<String, Object> updatedData) { await _dbRef.child(packId).update(updatedData); } /* Deleting an entry from firebase. This works perfectly fine */ void deleteRecord(packId) { _deletePack(packId); AsyncValue<List<Pack>> data = state; data.whenData((value) => value.removeWhere((pack) => pack.id == packId)); state = data; } void _deletePack(packId) async { await _dbRef.child(packId).remove(); } /* Creating a new entry in firebase. This works perfectly fine */ void insertRecord(pack) { insert(pack); AsyncValue<List<Pack>> data = state; data.whenData((value) => value.add(pack)); state = data; } void insert(pack) async { await _dbRef.push().set(pack); } } final PackProvider = StreamNotifierProvider<PackStreamNotifier, List<Pack>>( PackStreamNotifier.new);
问题描述
编辑Firebase中的条目时,我希望调用fetchData方法更新状态(而非像删除和插入那样手动修改状态),但fetchData在editRecord中调用后并未生效。Firebase数据库中的数据已成功更新,但Pack列表仍显示旧数据。
疑问:build方法中调用fetchData能在应用首次加载时正常初始化状态,为何在editRecord中调用同样的方法无法更新状态?
以下是调用editRecord的Widget代码:
class PackRecord extends ConsumerStatefulWidget { const WeightRecord({super.key}); final Pack pack; @override ConsumerState<PackRecord> createState() => _PackRecordState(); } class _PackRecordState extends ConsumerState<PackRecord> { void onPressEdit() async { // Ui code here to display an editing alert dialog box. when the user clicks OK, editValue function below is invoked. The code was too large to fit in here but is a simple alert dialog box, so it seems fitting to skip code. } void editValue(int weight, context) async { final newPack = {'weight': weight}; ref.read(PackProvider.notifier).editRecord(widget.pack.id, newPack); setState(() { Navigator.of(context).pop(); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Track your weight'), actions: [ IconButton( onPressed: () { onPressEdit(); }, icon: const Icon( Icons.mode_edit_outline_outlined, ), ), ], ), // Skipping the body since it isn't relevant to the problem mentioned. ); } }
问题原因与修复方案
核心问题
StreamNotifier的状态由build方法返回的流驱动,你在editRecord中调用fetchData只是返回了一个新流,但未让StreamNotifier重新监听这个新流,因此状态不会更新。_editPack方法缺少async关键字,导致await无法生效,可能出现数据库更新未完成就执行fetchData的情况。fetchData中手动设置state = AsyncValue.data(packs)属于冗余操作,StreamNotifier会自动处理流的yield值来更新状态。
修复步骤
1. 修复_editPack的异步问题
给方法添加async关键字,确保更新操作完成后再执行后续逻辑:
Future<void> _editPack(packId, Map<String, Object> updatedData) async { await _dbRef.child(packId).update(updatedData); }
2. 触发StreamNotifier重新拉取数据
使用ref.invalidateSelf()方法通知StreamNotifier重新执行build方法,从而获取最新数据:
Future<void> editRecord(packId, Map<String, Object> updatedData) async { await _editPack(packId, updatedData); // 通知StreamNotifier重新执行build,刷新数据 ref.invalidateSelf(); }
3. 清理fetchData的冗余代码
删除手动设置state的代码,让StreamNotifier自动处理流的输出:
Stream<List<Pack>> fetchData() async* { List<Pack> packs = []; try { final data = await _dbRef.once().then((value) => value.snapshot); for (var dataNode in data.children) { Map<dynamic, dynamic> map = dataNode.value as Map; map.putIfAbsent('id', () => dataNode.key); packs.add(formatIntoPack(map)); } yield packs; // 移除这行冗余代码:state = AsyncValue.data(packs); } catch (error) { throw Exception('failed to fetch data'); } }
4. 统一删除/插入的状态更新逻辑(可选)
当前删除和插入是手动修改本地状态,可能出现本地与数据库不一致的情况,建议统一使用ref.invalidateSelf():
Future<void> deleteRecord(packId) async { await _deletePack(packId); ref.invalidateSelf(); } Future<void> insertRecord(Pack pack) async { await insert(pack); ref.invalidateSelf(); }
额外优化建议
改用Firebase的实时监听(onValue)代替once(),这样数据库有变化时会自动推送更新,无需手动触发刷新:
@override Stream<List<Pack>> build() { return _dbRef.onValue.map((event) { List<Pack> packs = []; final data = event.snapshot; for (var dataNode in data.children) { Map<dynamic, dynamic> map = dataNode.value as Map; map.putIfAbsent('id', () => dataNode.key); packs.add(formatIntoPack(map)); } return packs; }).handleError((error) { throw Exception('failed to fetch data'); }); }
这种方式下,编辑、删除、插入操作后,Firebase会自动触发流的更新,状态会同步变化,连ref.invalidateSelf()都无需调用。
内容的提问来源于stack exchange,提问作者Tanish
相关产品推荐
相关产品推荐

