Flutter Error: "Widget cannot build because is already in the process of building"
Asked Answered
C

2

7

I got this error:

I/flutter (29346): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter (29346): The following assertion was thrown building MainLogic(dirty, state: _MainLogic#9c794):
I/flutter (29346): setState() or markNeedsBuild() called during build.
I/flutter (29346): This InhWidget widget cannot be marked as needing to build because the framework is already in the
I/flutter (29346): process of building widgets. A widget can be marked as needing to be built during the build phase
I/flutter (29346): only if one of its ancestors is currently building. This exception is allowed because the framework
I/flutter (29346): builds parent widgets before children, which means a dirty descendant will always be built.
I/flutter (29346): Otherwise, the framework might not visit this widget during this build phase.
I/flutter (29346): The widget on which setState() or markNeedsBuild() was called was:
I/flutter (29346):   InhWidget(state: InhState#3aaa8)
I/flutter (29346): The widget which was currently being built when the offending call was made was:
I/flutter (29346):   MainLogic(dirty, state: _MainLogic#9c794)

Before flooding you with code I'd like to explain a bit the logic behind: I'm trying to implement the rules of the game baccarat, to do so I set up an Inherited widget, some stateless widget send the input to an Object stored in the inherited state, according to this object data other widget rebuild themselves.

the last change I made is applying the baccarat "drawing rules", that can make the Object skip an input when required, but when this happens the inherited widget doesn't like it.

I'll post the last part of the code, but if you think that something else is relevant just ask and I'll post it.

thank you in advance for you help

    class MainLogic extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => new _MainLogic();
}
class _MainLogic extends State<MainLogic> {
  Widget cardGrid = CardGrid();
  Widget newGame = NewGame();
  Rules rules;
  bool gameOver;
  @override
  void initState(){
    super.initState();
    gameOver = false;
  }
  @override
  Widget build(BuildContext context) {
    final InhState state = InhWidget.of(context);
    rules = new Rules(game:state.game);
    if(gameOver==false && state.game.count>3 && rules.playerExtraCard()==false)
    {state.skip();}
    if(gameOver==false && state.game.count>4 && rules.bankerExtraCard()==false)
    {state.skip();}
    checkGame(state.game.count);
    return Container(child:status(gameOver));
  }
  status(bool _gameOver) {
    Widget whichOne;
    if (_gameOver) {whichOne=newGame;}
    else {whichOne=cardGrid;}
    return whichOne;
  }
  void checkGame(int cards) {
    if (cards >= 6) {
      gameOver = true;
    } else {gameOver = false;}
  }

...

   class Rules {
  final Game game;
  Rules({this.game});
  static Deck deck = new Deck();
  int get p1 => deck.getValue(game.p1);
  int get p2 => deck.getValue(game.p2);
  int get p3 => deck.getValue(game.p3);
  int get b1 => deck.getValue(game.b1);
  int get b2 => deck.getValue(game.b2);
  int get b3 => deck.getValue(game.b3);
  normalize(List<int> input) {
    int normal;
    int initial = 0;
    input.forEach((num e){initial += e;});
    if(initial>9) {normal=initial-10;}
    else {normal=initial;}
    return normal;
  }
  playerExtraCard() {
    bool extraCard;
    int pInit = normalize([p1,p2]);
    int bInit = normalize([b1+b2]);
    if (pInit > 5) {extraCard = false;}
    else if (bInit > 7) {extraCard = false;}
    else {extraCard = true;}
    return extraCard;
  }
  bankerExtraCard() {
    bool extraCard;
    int pInit = normalize([p1,p2]);
    int bInit = normalize([b1+b2]);
    int pWing = normalize([p3]);
    if (pInit>7) {extraCard = false;}
    else if (bInit>6) {extraCard = false;}
    else if (pInit>5 && bInit<6) {extraCard = true;}
    else if (pInit<6 && bInit==6 && pWing<8 && pWing>5) {extraCard = true;}
    else if (pInit<6 && bInit==5 && pWing<8 && pWing>3) {extraCard = true;}
    else if (pInit<6 && bInit==4 && pWing<8 && pWing>1) {extraCard = true;}
    else if (pInit<6 && bInit==3 && pWing==8) {extraCard = true;}
    else if (pInit<6 && bInit<3) {extraCard = true;}
    else {extraCard = false;}
    return extraCard;
  }
}

...

   class InhState extends State<InhWidget> {
  Game game = new Game();
  @override
  void initState() {
    super.initState();
    game.blank();
  }
  void deal(String card) {
    setState(() {
      game.cards[game.count] = card;
      game.count = game.count + 1;
    });
  }
  void restart(){
    setState(() {
      game.blank();
    });
  }
  void skip() {
    setState(() {
      game.count = game.count + 1;
    });
  }
  @override
  Widget build(BuildContext context) {
    return new InhCore(
      data: this,
      child: widget.child,
    );
  }
}

...

   class InhWidget extends StatefulWidget {
  InhWidget({this.child});
  final Widget child;
  @override
  State<StatefulWidget> createState() => InhState();
  static InhState of(BuildContext context) {
    return (context.inheritFromWidgetOfExactType(InhCore) as InhCore).data;
  }
}
Corporative answered 27/11, 2018 at 11:16 Comment(0)
R
6

You call state.skip() in build() and setState() in skip()

  void skip() {
    setState(() {
      game.count = game.count + 1;
    });
  } 

Usually it's a good idea to avoid "business logic" in build().

There shouldn't be a need to calculate all that stuff that you do using InhState in build(). build() can be called very often at various times.

You should rather update the state depending on user interaction events (onTap:, ...) or other event sources like streams or futures that indicate state changes from async operations.

Roadblock answered 27/11, 2018 at 11:40 Comment(1)
I haven't thought about building a stream 'inside' an inherited widget, to be completely honest I pictured inherited widget as alternative to stream (which I have brutally failed to implement successfully). I get your point, I started to build the 'rules' class when inhstate became really crowded, I'll try to implement a stream to handle it. thank you for your helpCorporative
S
1

I'd side with @Günter Zöchbauer's answer and refactor the code as he advises, but for quicker fix you can try to wrap the state.skip(); instructions (the ones inside the build()) into a WidgetsBinding.instance.addPostFrameCallback. This way you'd defer the state change operation to happen right after the build.

@override
Widget build(BuildContext context) {
  final InhState state = InhWidget.of(context);
  rules = new Rules(game:state.game);
  if (!gameOver) {
    if (state.game.count > 3 && !rules.playerExtraCard() ||
        state.game.count > 4 && !rules.bankerExtraCard())
    {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        state.skip();
      });
    }
  }

  checkGame(state.game.count);
  return Container(child:status(gameOver));
}

Unrelated, but I'd also advise you to use flutter format on your project to help you with your code style.

Scapegoat answered 31/5, 2021 at 6:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.