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

如何通过FutureProvider正确更新Freezed状态?报错求助

解决Riverpod中Widget构建时修改Provider的错误

错误信息

[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Tried to modify a provider while the widget tree was building.
If you are encountering this error, chances are you tried to modify a provider
in a widget life-cycle, such as but not limited to:

  • build
  • initState
  • dispose
  • didUpdateWidget
  • didChangeDepedencies

Modifying a provider inside those life-cycles is not allowed, as it could
lead to an inconsistent UI state. For example, two widgets could listen to the
same provider, but incorrectly receive different states.

To fix this problem, you have one of two solutions:

  • (preferred) Move the logic for modifying your provider outside of a widget
    life-cycle. For example, maybe you could update your provider inside a button's
    onPressed instead.

  • Delay your modification, such as by encasuplating the modification
    in a Future(() {...}).
    This will perform your update after the widget tree is done building.

相关代码

状态类

@freezed
class DevicePageState<T> with _$DevicePageState {
  const factory DevicePageState.initial() = Initial;
  const factory DevicePageState.loading() = Loading;
  const factory DevicePageState.loaded(AsyncValue<List<Device>> devices) = Loaded;
  const factory DevicePageState.error() = Error;
}

StateNotifier抽象类

abstract class IRepository extends StateNotifier<DevicePageState> {
  IRepository(DevicePageState devicePageState) : super(const DevicePageState.initial());

  Future<void> fetchDevices();
  Future<void> fetchTargetDevice();
  Future<void> updateDevice();
}

Repository实现

class DeviceApiRepository extends IRepository {
  final UPnPDiscoveryService upnpDiscoveryService;
  final Ref ref;

  DeviceApiRepository({required this.ref, required this.upnpDiscoveryService}) : super(const DevicePageState.loading());

  @override
  Future<void> fetchDevices() async {
    try {
      // state = const DevicePageState.loading();
      final devices = ref.watch(upnpDevicesProviderProvider);
      state = DevicePageState.loaded(devices);
    } catch (e) {
      state = const DevicePageState.error();
    }
  }

  @override
  Future<void> fetchTargetDevice() async {
    try {
      state = const DevicePageState.loading();
      final targetDevice = ref.watch(upnpDevicesProviderProvider);
      state = DevicePageState.loaded(targetDevice);
    } catch (e) {
      state = const DevicePageState.error();
    }
  }

  @override
  Future<void> updateDevice() async {
    try {
      state = const DevicePageState.loading();
      final devices = ref.watch(upnpDevicesProviderProvider);
      state = DevicePageState.loaded(devices);
    } catch (e) {
      state = const DevicePageState.error();
    }
  }
}

Widget调用代码

@override
Widget build(BuildContext context) {
  ref.read(deviceApiRepositoryProvider.notifier).fetchDevices();
  final data = ref.watch(deviceApiRepositoryProvider);
  // 构建UI...
}

解决方案

核心问题

错误根源是在build方法中调用fetchDevices,该方法会修改StateNotifier的state,而Riverpod禁止在Widget构建生命周期内修改Provider状态,避免UI不一致。


方案一:响应式自动监听依赖(推荐)

让Repository自动监听upnpDevicesProviderProvider的变化,无需手动调用fetchDevices,符合Riverpod的响应式设计:

修改DeviceApiRepository的实现:

class DeviceApiRepository extends IRepository {
  final UPnPDiscoveryService upnpDiscoveryService;
  final Ref ref;

  DeviceApiRepository({required this.ref, required this.upnpDiscoveryService}) : super(const DevicePageState.loading()) {
    // 监听设备数据变化,自动更新状态
    ref.listen<AsyncValue<List<Device>>>(upnpDevicesProviderProvider, (previous, next) {
      state = DevicePageState.loaded(next);
    });
    // 初始化时触发一次加载
    _initFetch();
  }

  Future<void> _initFetch() async {
    try {
      state = const DevicePageState.loading();
      final devices = ref.read(upnpDevicesProviderProvider);
      state = DevicePageState.loaded(devices);
    } catch (e) {
      state = const DevicePageState.error();
    }
  }

  // 保留其他方法的实现...
}

然后修改Widget代码,仅监听状态即可:

@override
Widget build(BuildContext context) {
  final data = ref.watch(deviceApiRepositoryProvider);
  // 根据data的状态构建UI(loading/loaded/error)
}

方案二:延迟调用(应急方案)

如果必须在Widget构建时触发数据获取,使用PostFrameCallback延迟到Widget树构建完成后执行:

@override
Widget build(BuildContext context) {
  WidgetsBinding.instance.addPostFrameCallback((_) {
    ref.read(deviceApiRepositoryProvider.notifier).fetchDevices();
  });
  final data = ref.watch(deviceApiRepositoryProvider);
  // 构建UI...
}

注意:此方案会在每次Widget重建时触发调用,建议添加判断条件避免重复执行,比如仅在状态为Initial时调用。


内容的提问来源于stack exchange,提问作者RootKid87

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 20:47:02