0

I'm experimenting with Provider, but I get the error setState() or markNeedsBuild() called during build, if I change data and use notifyListeners().

What I do:

I initialize mainElementList holding a simple object when starting the App:

void main() {
  List mainElementList = [MainElement()];
  runApp(MyApp(
    mainElementList: mainElementList,
  ));
}

For now MainElement only has one property:

class MainElement {
  bool allTasksDone = false;
  }

Then, still in main.dart, I use mainElementlist as argument to set up a Provider (MainElementList) wrapping the MaterialApp:

return MultiProvider(
      providers: [
        ChangeNotifierProvider(
          create: (context) => MainElementList(widget.mainElementList),
        )
      ],
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        title: 'Three Things',
        initialRoute: '/',
        routes: {
          '/': (ctx) => TabsScreen(),
        },
      ),
    );

The Provider class:

class MainElementList with ChangeNotifier {
  List _mainElementList;

  MainElementList(this._mainElementList);

  get mainElementList {
    return [..._mainElementList];
  }
    
  void toggleAllTasksDone() {
    _mainElementList[0].allTasksDone = !_mainElementList[0].allTasksDone;
    notifyListeners();
  }
}

A couple of steps down the widget tree, I'm testing, whether I can toggle allTasksDone; I do this in a stateless (edit: typo; it's stateful) widget's build method before the return statement:

MainElementList mainElementList = Provider.of<MainElementList>(context);
print('Before: ${mainElementList.mainElementList[0].allTasksDone}');
mainElementList.toggleAllTasksDone();
print('After: ${mainElementList.mainElementList[0].allTasksDone}');

It works in so far that the value is toggled. But if I use notifyListeners in .toggleAllTasksDone, I get this:

> Exception caught by foundation library
> ════════════════════════════════ The following assertion was thrown
> while dispatching notifications for MainElementList: setState() or
> markNeedsBuild() called during build.
> 
> This _InheritedProviderScope<MainElementList?> 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. The widget on which setState() or
> markNeedsBuild() was called was:
> _InheritedProviderScope<MainElementList?>
>     value: Instance of 'MainElementList'
>     listening to value The widget which was currently being built when the offending call was made was: MainElementWidget
>     dirty

Any help highly appreciated!

__ Edit: Full widget as per request:

class MainElementWidget extends StatefulWidget {
  const MainElementWidget({
    Key? key,
  }) : super(key: key);

  @override
  State<MainElementWidget> createState() => _MainElementWidgetState();
}

class _MainElementWidgetState extends State<MainElementWidget> {
  @override
  Widget build(BuildContext context) {
    //RESPONSIVENESS
    final availableWidth = context.read<DimensionsProvider>().availableWidth;
    final mainElementSize = availableWidth * 0.8;

    //COLORS
    final customColorScheme = context.read<CustomColors>().customColorScheme;

    //MAIN ELEMENT LIST
    MainElementList mainElementList = Provider.of<MainElementList>(context);
    print('Before: ${mainElementList.mainElementList[0].allTasksDone}');
    mainElementList.toggleAllTasksDone();
    print('After: ${mainElementList.mainElementList[0].allTasksDone}');

    return ClipRRect(
      borderRadius: BorderRadius.circular(15),
      child: Container(
        color: customColorScheme['Primary 3'],
        child: SizedBox(
          width: mainElementSize,
          height: mainElementSize,
        ),
      ),
    );
  }
}

Yes, I'm overusing maps ...sue me :)

Joe
  • 311
  • 3
  • 17
  • Can you try with `Provider.of(context,listen:false)` – Md. Yeasin Sheikh Feb 12 '22 at 17:42
  • Tried it, same result (I also really want to listen :)) – Joe Feb 12 '22 at 17:45
  • Then it is coming from different part i think, Can you include full widget that will reproduce the same issue? – Md. Yeasin Sheikh Feb 12 '22 at 17:53
  • Please add the full code of the statefulwidget at the end of the question – Peter Haddad Feb 12 '22 at 17:56
  • It's strange, because I'm not listenting or using that particular Provider anywhere else in the app. Just getting mainElementList at this one point to toggle allTasksDone and the print statements to see if it works. I'll edit the post and add the full widget. – Joe Feb 12 '22 at 17:58
  • 1
    @Joe what `maps` are you overusing? Anyway this error happens when the `build()` method is already building and then you call `notifyListener()` which will also rebuild, try to comment `context.read().availableWidth;` and `context.read().customColorScheme` just to see if that's the problem and check this also https://stackoverflow.com/questions/62432759/why-cant-i-use-context-read-in-build-but-i-can-use-provider-of-with-listen – Peter Haddad Feb 12 '22 at 18:24
  • 1
    hey....forget the maps ... was a misguided attempt for a joke while trying to discuss complicated things. My bad! I tried commenting out the other providers. No change. – Joe Feb 12 '22 at 18:34
  • The problem really seems to be calling notifyListeners in a build method...but I thought this is what changeNotifers() is there for. – Joe Feb 12 '22 at 18:37
  • What happens if you call `toggleAllTasksDone` in a `postFrameCallback`, like `WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {mainElementList.toggleAllTasksDone();})`? – Peter Koltai Feb 12 '22 at 18:51
  • It works! Thanks fur guiding me that far! But: I was under the impression that it is normal to call a method in a provider to change the provider's data and then do notifyListeners() ... all the examples I looked at before I tried myself were able to do stuff wihtout postFrameCallback. It leaves me a bit confused and unsure, if I should proceed buildung on this ... I'm trying to construct a good solution to build my app upon. – Joe Feb 12 '22 at 19:29
  • I think I unddertsand something now: I'm not just grabbing data before the return statement, I'm trying to manipulate it directly, before build is done. I'll do more experiments tomorrow. I'm out of time. – Joe Feb 12 '22 at 19:41

0 Answers0