Home > Software design >  setState() or markNeedsBuild() called during build, widget was currently being built when offending
setState() or markNeedsBuild() called during build, widget was currently being built when offending

Time:06-23

I am trying to make a simple quiz app using flutter but this has been bugging me for over a day now

The error mainly states that the myApp was called during the build of Quiz. I have no idea what to do even I have searched on youtube too.

Error message

Main.dart code:

import 'package:first/result.dart';
import 'package:flutter/material.dart';

import './quiz.dart';
import './result.dart';

void main(List<String> args) => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return _MyAppState();
  }
}

class _MyAppState extends State<MyApp> {
  final _questions = [
    {
      'questionText': 'What\'s your favorite colour?',
      'answers': ['Black', 'Red', 'Blue', 'Green'],
    },
    {
      'questionText': 'What\'s your favorite animal?',
      'answers': ['Cat', 'Dog', 'Pig', 'Parrot'],
    },
    {
      'questionText': 'Who\'s your favorite wrestler?',
      'answers': ['Seth Rollins', 'Roman Reigns', 'John Cena', 'Randy Orton'],
    },
  ];
  var _questionIndex = 0;

  void _answerQuestion() {
    setState(() {});
    _questionIndex = _questionIndex   1;
    print(_questionIndex);

    if (_questionIndex < _questions.length) {
      print('We have more Questions for you !!');
    } else {
      print('No more questions left !!');
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('My First App'),
        ),
        body: _questionIndex < _questions.length
            ? Quiz(
                answerQuestion: _answerQuestion,
                questionIndex: _questionIndex,
                questions: _questions)
            : Result(),
      ),
    );
  }
}

quiz.dart code:


import './question.dart';
import './answer.dart';

class Quiz extends StatelessWidget {
  final List<Map<String, Object>> questions;
  final int questionIndex;
  final Function answerQuestion;

  Quiz({required this.questions, required this.answerQuestion, required this.questionIndex});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Question(questions[questionIndex]['questionText'] as String),
        ...(questions[questionIndex]['answers'] as List<String>).map((answer) {
          return Answer(answerQuestion(), answer);
        }).toList()
      ],
    );
  }
}

result.dart code:

import 'package:flutter/material.dart';

class Result extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('All questions are answered !!'),
    );
  }
}

answer.dart code:

import 'package:flutter/material.dart';

class Answer extends StatelessWidget {
  final VoidCallback selectHandler;
  final String answerText;

  Answer(this.selectHandler, this.answerText);

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: double.infinity,
      child: RaisedButton(
          color: Colors.blue,
          textColor: Colors.white,
          onPressed: selectHandler,
          child: Text(answerText)),
    );
  }
}

question.dart code:

import 'package:flutter/material.dart';

class Question extends StatelessWidget {
  final String questionText;

  Question(this.questionText);

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      margin: EdgeInsets.all(10.0),
      child: Text(questionText,
          style: TextStyle(fontSize: 28), textAlign: TextAlign.center),
    );
  }
}

Help please.

CodePudding user response:

The reason you are getting the error message is that you are calling setState every time the build method of the MyApp widget is called. The setState method will re-trigger the build of the widget, and then setState is called again. So basically you have an infinite re-render.

If you look at your code in quiz.dart we find the reason for this infinite re-render:

@override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Question(questions[questionIndex]['questionText'] as String),
        ...(questions[questionIndex]['answers'] as List<String>).map((answer) {
          return Answer(answerQuestion() /*<-- here is the problem*/, answer);
        }).toList()
      ],
    );
  }

Every time the quiz widget calls it build method, it will call the answerQuestion method, which will call the build method of your main widget, which in turn will rebuild the quiz widget. Instead of calling the method here, you should send this callback method further down the three to the answer widget:

class Quiz extends StatelessWidget {
  final List<Map<String, Object>> questions;
  final int questionIndex;
  final Function answerQuestion;

  Quiz({required this.questions, required this.answerQuestion, required this.questionIndex});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Question(questions[questionIndex]['questionText'] as String),
        ...(questions[questionIndex]['answers'] as List<String>).map((answer) {
          return Answer(answerQuestion /* Notice the difference */, answer);
        }).toList()
      ],
    );
  }
}
class Answer extends StatelessWidget {
  final Function answerQuestion; // I renamed this to answerQuestion, feel free to stick with selectHandler
  final String answerText;

  Answer(this.answerQuestion, this.answerText);

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: double.infinity,
      child: RaisedButton(
          color: Colors.blue,
          textColor: Colors.white,
          onPressed: answerQuestion,
          child: Text(answerText)),
    );
  }
}

I also would advise you to update the logic in your answerQuestion method widget, so you wrap your setState method around the state update you are doing:

void _answerQuestion() {
    setState(() {
       _questionIndex = _questionIndex   1;
    });
    print(_questionIndex);

    if (_questionIndex < _questions.length) {
      print('We have more Questions for you !!');
    } else {
      print('No more questions left !!');
    }
  }

CodePudding user response:

As per the code you provided, it seems you are only passing instances of the functions as in _answerQuestion in your main.dart code and further into other files. As per my experience of working with dart and flutter, it is highly recommended that when one is passing a function, to a widget, one should always pass it in the following manner:

Quiz(
                answerQuestion: () => _answerQuestion(), // pass it like this to avoid errors.
                questionIndex: _questionIndex,
                questions: _questions)

That is, you should pass it as a lambda/Anonymous function. i.e.

void yourFunction() {
  // You do something here
}

AnyWidget(
  onPressed: () => yourFunction();, // This is called a lambda/anonymous function.
);

The reason for this is that the function when passed like this onPressed: yourFunction, would get called even when user hasn't actually interacted with the app, i.e. it gets called when building the widget. To avoid that, we use lambda functions.

If my answer was helpful, please mark my answer as Correct. Thank you!

CodePudding user response:

The main issue occurs on Answer and Quiz widget while using Function.

Use VoidCallback on Quiz instead of Funtion.

class Quiz extends StatelessWidget {
  final List<Map<String, Object>> questions;
  final int questionIndex;
  final VoidCallback answerQuestion;

  const Quiz({
    Key? key,
    required this.questions,
    required this.answerQuestion,
    required this.questionIndex,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Question(questions[questionIndex]['questionText'] as String),
        ...(questions[questionIndex]['answers'] as List<String>).map(
          (answer) {
            return Answer(answerQuestion, answer);
          },
        ).toList()
      ],
    );
  }
}

Full snippet

import 'package:flutter/material.dart';

void main(List<String> args) => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return _MyAppState();
  }
}

class _MyAppState extends State<MyApp> {
  final _questions = [
    {
      'questionText': 'What\'s your favorite colour?',
      'answers': ['Black', 'Red', 'Blue', 'Green'],
    },
    {
      'questionText': 'What\'s your favorite animal?',
      'answers': ['Cat', 'Dog', 'Pig', 'Parrot'],
    },
    {
      'questionText': 'Who\'s your favorite wrestler?',
      'answers': ['Seth Rollins', 'Roman Reigns', 'John Cena', 'Randy Orton'],
    },
  ];
  var _questionIndex = 0;

  void _answerQuestion() {
    setState(() {});
    _questionIndex = _questionIndex   1;
    print(_questionIndex);

    if (_questionIndex < _questions.length) {
      print('We have more Questions for you !!');
    } else {
      print('No more questions left !!');
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('My First App'),
        ),
        body: _questionIndex < _questions.length
            ? Quiz(
                answerQuestion: _answerQuestion,
                questionIndex: _questionIndex,
                questions: _questions)
            : Result(),
      ),
    );
  }
}

class Quiz extends StatelessWidget {
  final List<Map<String, Object>> questions;
  final int questionIndex;
  final VoidCallback answerQuestion;

  const Quiz({
    Key? key,
    required this.questions,
    required this.answerQuestion,
    required this.questionIndex,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Question(questions[questionIndex]['questionText'] as String),
        ...(questions[questionIndex]['answers'] as List<String>).map(
          (answer) {
            return Answer(answerQuestion, answer);
          },
        ).toList()
      ],
    );
  }
}

class Answer extends StatelessWidget {
  final VoidCallback selectHandler;
  final String answerText;

  Answer(this.selectHandler, this.answerText);

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: double.infinity,
      child: RaisedButton(
        color: Colors.blue,
        textColor: Colors.white,
        onPressed: selectHandler,
        child: Text(answerText),
      ),
    );
  }
}

class Question extends StatelessWidget {
  final String questionText;

  Question(this.questionText);

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      margin: EdgeInsets.all(10.0),
      child: Text(questionText,
          style: TextStyle(fontSize: 28), textAlign: TextAlign.center),
    );
  }
}

class Result extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('All questions are answered !!'),
    );
  }
}
  • Related