0

I'm trying to execute a callback function once a timer ends in a particular widget but I keep getting this exception:

I/flutter (16413): Another exception was thrown: setState() or markNeedsBuild() called during build.

So I have this widget called countdown:

class Countdown extends StatefulWidget {
  final VoidCallback onCountdownExpire;

  Countdown(this.onCountdownExpire);

  @override
  CountdownState createState() => CountdownState();
}

class CountdownState extends State<Countdown> with TickerProviderStateMixin {
  AnimationController controller;

  String get timerString {
    Duration duration = controller.duration * controller.value;
    return '${duration.inMinutes}:${(duration.inSeconds % 60).toString()}';
  }

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: 2),
    )..addStatusListener((AnimationStatus status){
        if (status == AnimationStatus.completed)
          widget.onCountdownExpire();
    });
    controller.reverse(from: 1.0);
  } 
   ...
   ...
   ... // omitted code
}

So once the animation is completed it will call the callback function:

class _QuizPageState extends State<QuizPage> {
  ... // omitted code  

  @override
  void initState() {
   ... // omitted code
  }

  void onCountdownExpire() {
    setState(() {
      _topContentImage =  AssetImage(questions[questionNum++].imagePath);
    });
  }
  ... // omitted code
}

I've tried to follow a solution but it does not work and gives me the same exception:

  void onCountdownExpire() => 
    setState(() {
      _topContentImage =  AssetImage(questions[questionNum++].imagePath);
    });

I also tried this but to no avail:

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: 2),
    )..addStatusListener((AnimationStatus status) =>
        (status == AnimationStatus.completed) ?
          widget.onCountdownExpire():null
    );
    controller.reverse(from: 1.0);
  }
Rajdeep
  • 2,246
  • 6
  • 24
  • 51

1 Answers1

2

maybe try including 'dart:async':

import 'dart:async';

then try wrapping your call of the onCountdownExpire function in a short-lived Timer():

...
        Timer(
          Duration(milliseconds:50),
          () {
            if (status == AnimationStatus.completed)
              widget.onCountdownExpire();
          },
        );
...

this will make the setState() happen outside the build phase of the last frame of your animation.

The error occurs most likely because the Countdown() is being redrawn as part of the redraw of the QuizPage() widget. Adding the Timer() will force the update to happen outside of the build() scope, in an async fashion, and will still achieve the same results without the error.

loushou
  • 1,462
  • 9
  • 15
  • ok i tried that and got this exception instead `I/flutter (16413): Another exception was thrown: RangeError (index): Invalid value: Not in range 0..1, inclusive: 2` – Rajdeep May 31 '19 at 03:19
  • did you do a full restart? or just a hot reload? because this change was in the initState() method, you would need a full restart. also, the truncated error messaged ('Another exception...') usually means that a previous exception was thrown prior to that one. it would be helpful to understand what that exception was in order to narrow down the problem. – loushou May 31 '19 at 03:32
  • ok I think I know why range error is being thrown, its because `questions` only has 2 elements and it is calling [2], which is wrong. And the reason this is happening is because when the animation starts, it is somehow "completed" as I inserted a debug print just before calling `windget.onCountdownExpire()` and it prints when the animation starts not when it is finished. – Rajdeep May 31 '19 at 04:01
  • i did a full restart – Rajdeep May 31 '19 at 04:02
  • ok the reason it was status completed at the start : `controller.reverse(from: 1.0);`. After i changed it to `controller.animateTo(1.0);` it was completed at the end of the animation. – Rajdeep May 31 '19 at 04:07
  • I could also use `controller.dismissed` instead of `controller.completed` to solve the completed at the start issue – Rajdeep May 31 '19 at 04:16