River_pod: StateNotifier throws Unhandled Exception: setState() or markNeedsBuild() called during build.

Created on 8 Oct 2020  路  5Comments  路  Source: rrousselGit/river_pod

Describe the bug

I cloned Resocoder's Weather Bloc repository to migrate it to riverpod. I have one error. It shows:

E/flutter (21803): This UncontrolledProviderScope widget cannot be marked as needing to build because the framework is already in the process of building widgets.  A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
E/flutter (21803): The widget on which setState() or markNeedsBuild() was called was:
E/flutter (21803):   UncontrolledProviderScope
E/flutter (21803): The widget which was currently being built when the offending call was made was:
E/flutter (21803):   Builder

To Reproduce

In this file https://github.com/campanagerald/flutter-bloc-library-v1-tutorial/blob/migrate-to-riverpod/lib/notifiers/weather_state_notifier.dart

the error thrown when I change the state to WeatherLoading() again.

void getDetailedWeather(String cityName) async {
    state = WeatherLoading(); // error

    try {
      final weather = await weatherRepository.fetchDetailedWeather(cityName);
      state = WeatherLoaded(weather);
    } on NetworkError {
      state = WeatherError("Couldn't fetch weather. Is the device online?");
    }
}

Expected behavior
I am expecting that the consumer will rebuild without error.

Also I would like to create a pull request to add this to riverpod examples when it works.

bug

Most helpful comment

That's the expected behavior, and your fix is correct

Modifying objects synchronously inside widgets life cycles is not allowed as that can lead to a bad state

All 5 comments

it worked when I use the init state to get the detailed weather.

I changed this:

  @override
  void didChangeDependencies() {
    context
        .read(weatherStateNotifierProvider)
        .getDetailedWeather(widget.masterWeather.cityName);
    super.didChangeDependencies();
  }

to this

  @override
  void initState() {
    Future.delayed(
        Duration.zero,
        () => context
            .read(weatherStateNotifierProvider)
            .getDetailedWeather(widget.masterWeather.cityName));
    super.initState();
  }

I am not sure why.

That's the expected behavior, and your fix is correct

Modifying objects synchronously inside widgets life cycles is not allowed as that can lead to a bad state

I have another question. How to use ProviderListener with StateNotifierProvider?

ProviderListener<WeatherStateNotifier>(
 provider: weatherStateNotifierProvider,
 onChange: (_, weatherStateNotifier) {
  if (weatherStateNotifier.state is WeatherError) { // show a warning The member 'state' can only be used within instance members of subclasses of 'package:state_notifier/state_notifier.dart'.
   Scaffold.of(context).showSnackBar(
    SnackBar( content: Text((weatherStateNotifier.state as WeatherError).message),),);
 }
}

Instead of that ProviderListener use SchedulerBinding to show the SnackBar

SchedulerBinding.instance.addPostFrameCallback((_) {
  Scaffold.of(context).showSnackBar(SnackBar(
   content: Text((state.message))));
});

The syntax is:

provider: myProvider.state

Alright. Thanks!

Was this page helpful?
0 / 5 - 0 ratings