skip to Main Content

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


  1. You can do this as follows:

    class Example extends ConsumerWidget {
      @override
      Widget build(BuildContext context, WidgetRef ref) {
        final state = ref.watch(deviceApiRepositoryProvider);
    
        const factory DevicePageState.initial() = Initial;
      const factory DevicePageState.loading() = Loading;
      const factory DevicePageState.loaded(AsyncValue<List<Device>> devices) = Loaded;
      const factory DevicePageState.error() = Error;
    
        return state.when(
          initial: () => ...,
          loading: () => CircularProgressIndicator(),
          error: () => Text('Oops, something unexpected happened'),
          loaded: (List<Device> devices) => Text('$devices'),
        );
      }
    }
    

    Your fetch method (or use Notifier and build method):

      @override
      Future<void> fetchDevices() async {
        state = const DevicePageState.loading();
        try {
          // you cannot use watch here
          // or, consider using `Notifier<DevicePageState>` with the `build` method
          final devices = ref.read(upnpDevicesProviderProvider);
          state = DevicePageState.loaded(devices);
        } catch (e) {
          state = const DevicePageState.error();
        }
      }
    

    Note that you do not need to use AsyncValue<List<Device>> devices. Consider using AsyncNotifierProvider.

    Login or Signup to reply.
  2. You may try FutureBuilder. You can assign the future var everywhere.
    https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search