Flutter : Timer.periodic running multiple times in each iteration

I am trying to make snake 2 in flutter. And I have used Timer.periodic() for game loop. And I tried specifying duration as 1 seconds. But the code inside the Timer.periodic() runs multiple times in a second. I also tried debugging (though I am terrible at that) and found that the code inside the Timer.periodic() ran multiple times without stepping out of it. Though while debugging this couild happen as the code pauses for input. But I'm not sure about anything .Here is my code -

import 'dart:async';
import 'dart:math';

import 'package:flutter/material.dart';

class SnakePage extends StatefulWidget {
  @override
  _SnakePageState createState() => _SnakePageState();
}

class _SnakePageState extends State<SnakePage> {
  int score = 0;
  String swipe = '';
  bool running = false;
  int iterates = 0;
  List snake = [
    [
      [4, 3],
      1,
      true
    ],
    [
      [4, 2],
      1,
      false
    ],
    [
      [4, 1],
      1,
      false
    ],
  ];

  // Convert radians to degree
  double radians(double degree) {
    return ((degree * 180) / pi);
  }

  void turn(moveEvent) {
       double angle = radians(moveEvent.delta.direction);
      if (angle >= -45 && angle <= 45) {
        this.swipe = 'Swipe Right';
      } else if (angle >= 45 && angle <= 135) {
        this.swipe = 'Swipe Down';
      } else if (angle <= -45 && angle >= -135) {
        this.swipe = 'Swipe Up';
      } else {
        this.swipe = 'Swipe Left';
      }
  }

  int toIndex(coOrdinates) {
    return ((coOrdinates[0] + 1) * 10) + coOrdinates[1];
  }

  void run() {
    this.running = true;
    Timer.periodic(
        Duration(
          milliseconds: 500,
        ), (timer) {
      this.setState(() {
        this.iterates += 1;
        this.swipe = this.iterates.toString();
        for (var i = 0; i < this.snake.length; i++) {
          this.snake[i][0][1] += 1;
          if (this.snake[i][0][1] == 10) {
            this.snake[i][0][1] = 0;
          }
        }
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('WC'),
      ),
      body: Listener(
            onPointerMove: this.running
                ? (moveEvent) => this.turn(moveEvent)
                : (moveEvent) => this.run(),// Where the function is being called
            child: Container();
    );
  }
}

And please pardon me for code being a mess and not well commented. Any Help would be appreciated!


Solution 1:

The problem is that, every time you execute the run() method, a new timer is created and you listen for it, again. The old timer is not stopped, so it keeps firing. The solution is, before you create a timer, cancel the previous one. Something like this:

class _SnakePageState extends State<SnakePage> {
   Timer _myTimer;

void run() {
    this.running = true;
    _myTimer?.cancel(); //in case we have a timer, we'll cancel it.
    _myTimer = Timer.periodic(. // assing new timer to our variable.
        Duration(
          milliseconds: 500,
        ), (timer) {
      this.setState(() {
        this.iterates += 1;
        this.swipe = this.iterates.toString();
        for (var i = 0; i < this.snake.length; i++) {
          this.snake[i][0][1] += 1;
          if (this.snake[i][0][1] == 10) {
            this.snake[i][0][1] = 0;
          }
        }
      });
    });
  }

 
}