Search code examples
flutterexceptionflutter-provider

Need help chasing down: Exception while building using Provider in Flutter


I'm trying to learn Flutter and become more acquainted with passing data around. So i have this very simple app here that is a sort of complicated version of this: Provider version flutter starter demo

Like I said I'm trying to get acquainted, and I'm a relatively green dev. I'm creating this demo to learn StateManagement as well as Persistence.

My goal with this post, is to get help to fix this issue and also know what I'm doing wrong.

So far I have tried moving a few things around and some typical searches but can't seem to figure out specifically what I'm doing wrong here compared with others who are getting the same error.

The app works fine, exactly as expected, no crash and as far as my green grass eyes can tell my code is structured exactly like the Flutter example (with respect to the Provider StateManagement). However I'm getting this error in the console:

======== Exception caught by foundation library ====================================================
The following assertion was thrown while dispatching notifications for Keeper:
setState() or markNeedsBuild() called during build.

This _InheritedProviderScope<Keeper> 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<Keeper>
  value: Instance of 'Keeper'
  listening to value
The widget which was currently being built when the offending call was made was: Consumer<Keeper>
  dirty
  dependencies: [_InheritedProviderScope<Keeper>]

Page 1

class ScreenOne extends StatelessWidget {
  static const String id = 'screen_one';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          'Page One',
        ),
      ),
      backgroundColor: Colors.grey.shade200,
      body: Container(
        child: SafeArea(
          child: Padding(
            padding: EdgeInsets.all(20.0),
            child: Center(
              child: Column(
                children: [
                  CustomTextBoxes(title: 'Screen One'),
                  Consumer<Keeper>(
                    builder: (_, keeper, child) => Text(
                      '${keeper.pageOneValue}',
                      style: TextStyle(color: Colors.grey, fontSize: 20.0),
                    ),
                  ),
                  CustomTextBoxes(title: 'Screen Two'),
                  Consumer<Keeper>(
                    builder: (_, keeper, child) => Text(
                      '${keeper.pageTwoValue}',
                      style: TextStyle(color: Colors.grey, fontSize: 20.0),
                    ),
                  ),
                  CustomTextBoxes(title: 'Total'),
                  Consumer<Keeper>(
                    builder: (_, keeper, child) => Text(
                      '${keeper.addCounters()}',
                      style: TextStyle(color: Colors.grey, fontSize: 20.0),
                    ),
                  ),
                  SizedBox(
                    height: 20.0,
                  ),
                  CustomButton(
                    text: 'Screen 2',
                    function: () {
                      Navigator.pushNamed(context, ScreenTwo.id);
                    },
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
      floatingActionButton: CustomFloatingButton(
        function: () {
          var counter = context.read<Keeper>();
          counter.incrementCounterOne();
        },
      ),
    );
  }
}

"Keeper"

class Keeper with ChangeNotifier {
  int pageOneValue = 0;
  int pageTwoValue = 0;
  int totalValue = 0;

  void incrementCounterOne() {
    pageOneValue += 1;
    notifyListeners();
  }

  void incrementCounterTwo() {
    pageTwoValue += 1;
    notifyListeners();
  }

  int addCounters() {
    totalValue = pageOneValue + pageTwoValue;
    notifyListeners();
    return totalValue;
  }
}

Main

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => Keeper(),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(primarySwatch: Colors.blue),
      initialRoute: ScreenOne.id,
      routes: {
        ScreenOne.id: (context) => ScreenOne(),
        ScreenTwo.id: (context) => ScreenTwo()
      },
    );
  }
}

Solution

  • Your problem is in calling addCounters() inside your build method. addCounters() calls notifyListeners() that triggers a setState().

    This cannot be perform within your build function. It can only be performed later at the request of a User action. For example, inside the onPressed of a button as you do for the incrementCounterOne().

    Instead of computing and storing the total value of your two counters, you could use a getter:

    Keeper:

    class Keeper with ChangeNotifier {
      int pageOneValue = 0;
      int pageTwoValue = 0;
    
      int get totalValue => pageOneValue + pageTwoValue;
    
      void incrementCounterOne() {
        pageOneValue += 1;
        notifyListeners();
      }
    
      void incrementCounterTwo() {
        pageTwoValue += 1;
        notifyListeners();
      }
    }
    

    ScreenOne:

    Consumer<Keeper>(
      builder: (_, keeper, child) => Text(
        '${keeper.totalValue}', // addCounters()}',
        style: TextStyle(color: Colors.grey, fontSize: 20.0),
      ),
    ),