Search code examples
flutterflutter-animation

Flutter setState is restarting animation


I have this timer working fine:

Widget timer(int index) {
    return Center(
      child: AnimatedBuilder(
        animation: timerAnimationList[index],
        builder: (context, child) {
          final double seconds = timerAnimationList[index].value / 1000;
          final int integer = seconds.truncate();
          final int decimals = (seconds % 1 * 1000).truncate();
          String decimalsString = decimals.toString();

          switch (decimalsString.length) {
            case 1:
              decimalsString = '00$decimals';
            case 2:
              decimalsString = '0$decimals';
          }

          return Text(
            '${integer.toString()}:$decimalsString',
            style: const TextStyle(fontSize: 50),
          );
        },
      ),
    );

It goes from 0 to 10 showing something like 2:453. When this timer is finished or stopped I show a button to start again. Inside this button, for testing purposes, I only have this:

setState(() {
    });

When adding this, the timer cotinues running like if I ran timerControllerList[index].forward()

Why is this happening? I also try to add a unique key to the Text() showing the timer but it did nothing.

More info: The animator controller is in a List because I have differents timers to play.

class _GamePageState extends State<GamePage> with TickerProviderStateMixin {
  List<dynamic> timerControllerList = [];
  List<Animation> timerAnimationList = [];

     @override
  void initState() {
    super.initState();
    initTimer();
} 

    void initTimer() {
        late AnimationController timerController;
        late Animation<int> timerAnimation;
        int timerCounter = 10;
    
timerController = AnimationController(
      vsync: this,
      duration: Duration(seconds: timerCounter),
    );

    timerAnimation =
        StepTween(begin: 0, end: timerCounter * 1000).animate(timerController)
          ..addStatusListener((status) {
            if (status == AnimationStatus.completed) {
              print('TIMER FINISHED');
            }
          });

    timerControllerList.add(timerController);
    timerAnimationList.add(timerAnimation);
  }

Widgets position:

Stack(
        children: [
          timer(),
          button(),
        ],

Extra info:

When trying to reproduce this here: https://zapp.run/edit/flutter-zp7q06llp7r0?entry=lib/main.dart&file=lib/main.dart I saw that having a normal button, without any animation and just an onPress with a setState, it doesn't create the same problem. For my case I have something different that I might be conflicting with the other animation:

SlideInUp(
        manualTrigger: true,
        delay: const Duration(seconds: 2),
        controller: (controller) => playAgainButtonController = controller,
        child: ElevatedButton...

When timer is stop() I forward() this button to be displayed. Video: https://www.youtube.com/shorts/TlPtGNkyQ5U

This is using this package: https://pub.dev/packages/animate_do (BTW, I installed if last week and popularity was 98% and today it's 76%. How come?)


Solution

  • I just found my issue. Nothing to do with any package, just the way I had my widgets.

    So I had something like this:

    Widget displayWidgets(int index) {
        if (countingDown) return countDown(); // initial countdown before the timer
    
        countingUp = true;
        timerControllerList[index].forward();
        blackBGControllerList[index].forward();
    
        return timer(index); // actual timer that I mentioned
      }
    

    So when I click on the button to update the state and with that some vars like countingUp I'm triggering the next forward()s as well. So the solution was to move those 3 lines to the initial countdown that I had before starting the timer (or it could be the initState, for example):

     void initCountDown() {
        countDownController = AnimationController(
          vsync: this,
          duration: Duration(seconds: countDownCounter),
        );
    
        countDownAnimation =
            IntTween(begin: countDownCounter, end: 0).animate(countDownController)
              ..addStatusListener((status) {
                if (status == AnimationStatus.completed) {
                  setState(() {
                    countingDown = false;
                    countingUp = true;
                    for (var i = 0; i < players; i++) {
                      timerControllerList[i].forward();
                      blackBGControllerList[i].forward();
                    }
                    countDownController.reset();
                  });
                }
              });
    
        countDownController.forward();
      }