简体   繁体   English

为什么在 Flutter 中添加定期 Timer 后,每秒钟都会重新初始化所有内容?

[英]Why everything is being reinitialized every second after adding periodic Timer in Flutter?

I'm trying to build a quiz app.我正在尝试构建一个测验应用程序。 When any user starts to participate in a quiz a timer starts and don't know why everything is being reinitialized every second.当任何用户开始参与测验时,计时器会启动,并且不知道为什么每秒钟都在重新初始化所有内容。 The boolean parameter 'answered' is being set as false every second.布尔参数 'answered' 每秒被设置为 false。 As a result the participant can answer the same question multiple times which leads to wrong result and not suppose to happen.结果,参与者可以多次回答相同的问题,这会导致错误的结果,并且不应该发生。 Here's a snippet-这是一个片段-

class MathQuizPlay extends StatefulWidget {
  final String quizId;
  MathQuizPlay({Key key, this.quizId}) : super(key: key);

  @override
  _MathQuizPlayState createState() => _MathQuizPlayState(this.quizId);
}

int total = 0;
int _correct = 0;
int _incorrect = 0;
int _notAttempted = 0;
int timer;
String showtimer;

class _MathQuizPlayState extends State<MathQuizPlay> {
  var quizId;
  _MathQuizPlayState(this.quizId);
  QuerySnapshot questionSnapshot;
  bool isLoading = true;

  getQuestionData(String quizId) async {
    return await Firestore.instance
        .collection('math')
        .document(quizId)
        .collection('QNA')
        .getDocuments();
  }

  @override
  void initState() {
    getQuestionData(quizId).then((value) {
      questionSnapshot = value;
      setState(() {
        total = questionSnapshot.documents.length;
        _correct = 0;
        _incorrect = 0;
        _notAttempted = questionSnapshot.documents.length;
        isLoading = false;
        timer = total * 15;
        showtimer = timer.toString();
      });
    });

    starttimer();
    super.initState();
  }

  @override
  void setState(fn) {
    if (mounted) {
      super.setState(fn);
    }
  }

  Questions getQuestionModelFromDatasnapshot(
      DocumentSnapshot questionSnapshot) {
    final Questions questionModel = Questions(
        question: questionSnapshot.data['question'],
        option1: questionSnapshot.data['option1'],
        option2: questionSnapshot.data['option2'],
        option3: questionSnapshot.data['option3'],
        option4: questionSnapshot.data['option4'],
        correctOption: questionSnapshot.data['correctOption'],
        answered: false);

    return questionModel;
  }

  void starttimer() async {
    const onesec = Duration(seconds: 1);
    Timer.periodic(onesec, (Timer t) {
      setState(() {
        if (timer < 1) {
          t.cancel();
          Navigator.pushReplacement(
              context,
              MaterialPageRoute(
                  builder: (context) => Result(
                        correct: _correct,
                        incorrect: _incorrect,
                        total: total,
                        notattempt: _notAttempted,
                        collection: 'math',
                        quizId: quizId,
                      )));
        } else {
          timer = timer - 1;
        }
        showtimer = timer.toString();
      });
    });
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.teal[300],
        title: Text("Questions",
            style: TextStyle(
              color: Colors.white,
            )),
        elevation: 5.0,
        centerTitle: true,
      ),
      body: isLoading
          ? Container(
              child: Center(child: CircularProgressIndicator()),
            )
          : SingleChildScrollView(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  SizedBox(
                    height: 10,
                  ),
                  Center(
                      child: Container(
                          height: 60,
                          width: 60,
                          decoration: BoxDecoration(
                            borderRadius: BorderRadius.circular(36),
                            border: Border.all(
                              width: 2.0,
                              color: Colors.red.withOpacity(0.8),
                            ),
                          ),
                          child: Center(
                              child: Text(
                            showtimer,
                            style: TextStyle(
                                fontWeight: FontWeight.w500,
                                fontSize: 19.0,
                                color: Colors.red.withOpacity(0.8)),
                          )))),
                  SizedBox(
                    height: 10,
                  ),
                  Center(child: Text('Tap on the option to select answer')),
                  SizedBox(
                    height: 10,
                  ),
                  questionSnapshot.documents == null
                      ? Container(
                          child: Center(
                            child: Text("No Data"),
                          ),
                        )
                      : ListView.builder(
                          itemCount: questionSnapshot.documents.length,
                          shrinkWrap: true,
                          physics: ClampingScrollPhysics(),
                          itemBuilder: (context, index) {
                            return Padding(
                              padding: const EdgeInsets.symmetric(
                                  vertical: 15.0, horizontal: 25),
                              child: QuizPlayTile(
                                questionModel: getQuestionModelFromDatasnapshot(
                                    questionSnapshot.documents[index]),
                                index: index,
                              ),
                            );
                          }),
                  SizedBox(
                    height: 30,
                  ),
                  Center(
                    child: RaisedButton(
                        padding:
                            EdgeInsets.symmetric(vertical: 18, horizontal: 60),
                        color: Colors.teal[300],
                        textColor: Colors.white,
                        shape: RoundedRectangleBorder(
                            borderRadius: new BorderRadius.circular(8.0)),
                        child: Text(
                          'Submit',
                          style: TextStyle(fontSize: 16),
                        ),
                        elevation: 7.0,
                        onPressed: () {
                          Navigator.pushReplacement(
                              context,
                              MaterialPageRoute(
                                  builder: (context) => Result(
                                        correct: _correct,
                                        incorrect: _incorrect,
                                        total: total,
                                        notattempt: _notAttempted,
                                        collection: 'math',
                                        quizId: quizId,
                                      )));
                        }),
                  ),
                  SizedBox(
                    height: 50,
                  )
                ],
              ),
            ),
    );
  }
}


ISSUE :问题 :

As you are calling setState everytime your timer completes a second .... it refreshes the your screen after every second.当您每次计时器完成一秒时调用 setState 时......它会在每一秒后刷新您的屏幕。 Hence the build function is re-called after every second rebuilding each any every line of the code inside that function.因此,在每秒重建该函数内的每一行代码后,都会重新调用 build 函数。 Now the main issue is that the function : 'getQuestionModelFromDatasnapshot' will always provide a new 'questionModel' with a value of the 'answered' parameter as false every second the screen gets refreshed.现在的主要问题是函数:'getQuestionModelFromDatasnapshot' 将始终提供一个新的 'questionModel',其 'answered' 参数的值在屏幕刷新的每一秒都为 false。

SOLUTION :解决方案 :

Now I have two solutions for you:现在我有两个解决方案给你:

  1. Good programming practice : My guess is that you call setState to update the timer value in your UI after every seconds.良好的编程习惯:我的猜测是您每隔几秒调用 setState 来更新 UI 中的计时器值。 Now her you have to realize that calling setState for just a small change is bad as it will refresh all the other widgets will be recreated too which is unnecessary.现在她必须意识到,仅仅为一个小的更改调用 setState 是不好的,因为它会刷新所有其他小部件,也将重新创建这是不必要的。 The good way is to use ChangeNotifierProvider which will listen to update in the timer and then wrap the UI Text showing the timer value with a Consumer.最好的方法是使用 ChangeNotifierProvider,它会监听计时器中的更新,然后用消费者包装显示计时器值的 UI 文本。 Hence whenever the timer value is updated .... It will only update the UI Text with timer value.因此,无论何时更新计时器值......它只会更新带有计时器值的 UI 文本。

  2. In case you want a quick solution : Instead of calling 'getQuestionModelFromDatasnapshot' method in the build function what you can do is intialize the whole ListView in the initstate as a variable.... like : ListView x = /* Your listview code containing that fucntion */ ..... Doing this ...... the widget wont be rebuilt every second.如果你想要一个快速的解决方案:而不是在构建函数中调用 'getQuestionModelFromDatasnapshot' 方法,你可以做的是将 initstate 中的整个 ListView 初始化为一个变量......比如:ListView x = /* 你的 listview 代码包含那个功能 */ ..... 这样做......小部件不会每秒重建。

The code is a bit messy ....... I will strongly prefer to either re-write the code properly or use the ChangeNotifierProvider as I said in the first option.代码有点乱.......我非常喜欢正确地重新编写代码或使用我在第一个选项中所说的 ChangeNotifierProvider 。

I couldn't solve the problem using ChangeNotifierProvider either.我也无法使用 ChangeNotifierProvider 解决问题。 But luckily I found a package which completely solved my issue.但幸运的是,我找到了一个完全解决我的问题的软件包。 So I used that package instead of periodic Timer for setting the time.所以我使用那个包而不是定期 Timer 来设置时间。 Here's the update-这是更新-

import 'package:circular_countdown_timer/circular_countdown_timer.dart';

class PhyQuizPlay extends StatefulWidget {
  final String quizId;
  PhyQuizPlay({Key key, this.quizId}) : super(key: key);

  @override
  _PhyQuizPlayState createState() => _PhyQuizPlayState(this.quizId);
}

int total = 0;
int _correct = 0;
int _incorrect = 0;
int _notAttempted = 0;
int timer;

class _PhyQuizPlayState extends State<PhyQuizPlay> {
  var quizId;
  _PhyQuizPlayState(this.quizId);
  QuerySnapshot questionSnapshot;
  bool isLoading = true;

  getQuestionData(String quizId) async {
    return await Firestore.instance
        .collection('physics')
        .document(quizId)
        .collection('QNA')
        .getDocuments();
  }

  @override
  void initState() {
    getQuestionData(quizId).then((value) {
      questionSnapshot = value;
      setState(() {
        total = questionSnapshot.documents.length;
        _correct = 0;
        _incorrect = 0;
        _notAttempted = total;
        isLoading = false;
        timer = total * 15;
      });
    });
    super.initState();
  }

  Questions getQuestionModelFromDatasnapshot(
      DocumentSnapshot questionSnapshot) {
    final Questions questionModel = Questions(
        question: questionSnapshot.data['question'],
        option1: questionSnapshot.data['option1'],
        option2: questionSnapshot.data['option2'],
        option3: questionSnapshot.data['option3'],
        option4: questionSnapshot.data['option4'],
        correctOption: questionSnapshot.data['correctOption'],
        answered: false);

    return questionModel;
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.teal[300],
        title: Text("Questions",
            style: TextStyle(
              color: Colors.white,
            )),
        elevation: 5.0,
        centerTitle: true,
      ),
      body: isLoading
          ? Container(
              child: Center(child: CircularProgressIndicator()),
            )
          : SingleChildScrollView(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  SizedBox(
                    height: 10,
                  ),
                  Center(
                      child: CircularCountDownTimer(
                    width: 80,
                    height: 80,
                    duration: timer,
                    fillColor: Colors.red,
                    color: Colors.white38,
                    isReverse: true,
                    onComplete: () {
                      Navigator.pushReplacement(
                          context,
                          MaterialPageRoute(
                              builder: (context) => Result(
                                    correct: _correct,
                                    incorrect: _incorrect,
                                    total: total,
                                    notattempt: _notAttempted,
                                    collection: 'physics',
                                    quizId: quizId,
                                  )));
                    },
                  )),
                  SizedBox(
                    height: 10,
                  ),
                  Center(child: Text('Tap on the option to select answer')),
                  SizedBox(
                    height: 10,
                  ),
                  questionSnapshot.documents == null
                      ? Center(
                          child: CircularProgressIndicator(),
                        )
                      : ListView.builder(
                          itemCount: questionSnapshot.documents.length,
                          shrinkWrap: true,
                          physics: ClampingScrollPhysics(),
                          itemBuilder: (context, index) {
                            return Padding(
                              padding: const EdgeInsets.symmetric(
                                  vertical: 15.0, horizontal: 25),
                              child: QuizPlayTile(
                                questionModel: getQuestionModelFromDatasnapshot(
                                    questionSnapshot.documents[index]),
                                index: index,
                              ),
                            );
                          }),
                  SizedBox(
                    height: 30,
                  ),
                  Center(
                    child: RaisedButton(
                        padding:
                            EdgeInsets.symmetric(vertical: 18, horizontal: 60),
                        color: Colors.teal[300],
                        textColor: Colors.white,
                        shape: RoundedRectangleBorder(
                            borderRadius: new BorderRadius.circular(8.0)),
                        child: Text(
                          'Submit',
                          style: TextStyle(fontSize: 16),
                        ),
                        elevation: 7.0,
                        onPressed: () {
                          Navigator.pushReplacement(
                              context,
                              MaterialPageRoute(
                                  builder: (context) => Result(
                                        correct: _correct,
                                        incorrect: _incorrect,
                                        total: total,
                                        notattempt: _notAttempted,
                                        collection: 'physics',
                                        quizId: quizId,
                                      )));
                        }),
                  ),
                  SizedBox(
                    height: 50,
                  )
                ],
              ),
            ),
    );
  }
} 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM