You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

修改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.
    );
  }
}

问题原因与修复方案

核心问题

  1. StreamNotifier的状态由build方法返回的流驱动,你在editRecord中调用fetchData只是返回了一个新流,但未让StreamNotifier重新监听这个新流,因此状态不会更新。
  2. _editPack方法缺少async关键字,导致await无法生效,可能出现数据库更新未完成就执行fetchData的情况。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 17:03:12