Im using riverpod FutureProvider to fetch devices with API calls and try to persist them with StateNotifier in the state with custom state class. If i try to update the loading state i get following error. Any idea?
[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.
state class
@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;
}
state notifier
abstract class IRepository extends StateNotifier<DevicePageState> {
IRepository(DevicePageState devicePageState) : super(const DevicePageState.initial());
Future<void> fetchDevices();
Future<void> fetchTargetDevice();
Future<void> updateDevice();
}
repo
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();
}
}
}
The error was thrown in the fetchDevices after i try to reassign state.
Thank you
Edit:
I call the fetchDevices method in a build method of the ConsumerWidget
@override
Widget build(BuildContext context) {
ref.read(deviceApiRepositoryProvider.notifier).fetchDevices();
final data = ref.watch(deviceApiRepositoryProvider);
2
Answers
You can do this as follows:
Your
fetch
method (or use Notifier andbuild
method):Note that you do not need to use
AsyncValue<List<Device>> devices
. Consider using AsyncNotifierProvider.You may try FutureBuilder. You can assign the future var everywhere.
https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html