Home > database >  Timer doesn't cancel - Flutter Provider
Timer doesn't cancel - Flutter Provider

Time:10-01

The objective of my app is when I tap on start button, a timer should start which will run a function every 5 seconds, and when stopped the timer should cancel and stop that function from running. (Now I have different screens for the same purpose so I made a common screen and now I'm sending a Timer timer to that common stateful class constructor). So, the problem is, the timer doesn't cancel, when I tap start the function does executes every 5 seconds but doesn't stop. Below I have provided some relevant code snippets to my query.

Main screen:

class StrategyOneScreen extends StatefulWidget {
  const StrategyOneScreen({Key? key}) : super(key: key);

  @override
  State<StrategyOneScreen> createState() => _StrategyOneScreenState();
}

class _StrategyOneScreenState extends State<StrategyOneScreen> {
  @override
  Widget build(BuildContext context) {
    Timer timer1 = Timer(Duration(), () {});

    return StrategyScreen(
      stratName: 'Free Strategy',
      timer: timer1,
      toggle: Provider.of<StrategyProvider>(
        context,
        listen: true,
      ).companyStratOneToggle,
      toggleTimer: Provider.of<StrategyProvider>(
        context,
        listen: false,
      ).toggleCompanyTimer,
      setToggle: Provider.of<StrategyProvider>(
        context,
        listen: false,
      ).setCompanyStratOneToggle,
      chartLiveData: Provider.of<StrategyProvider>(
        context,
        listen: true,
      ).companyChartLiveData,
    );
  }
}

Inside the common StrategyScreen:

class StrategyScreen extends StatefulWidget {
  const StrategyScreen({
    Key? key,
    required this.stratName,
    required this.timer,
    required this.toggle,
    required this.toggleTimer,
    required this.setToggle,
    required this.chartLiveData,
  }) : super(key: key);

  final stratName;
  final timer;
  final toggle;
  final toggleTimer;
  final setToggle;
  final chartLiveData;

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

class _StrategyScreenState extends State<StrategyScreen> {

 @override
  Widget build(BuildContext context) {
    print("Timer: ${widget.timer}"); // console logs:=> Timer: null
    return Scaffold(
      ...
      Row(
            children: [
              Expanded(
                child: Center(
                  child: FloatingActionButton.extended(
                    heroTag: 'btn1',
                    onPressed: widget.toggle == false
                        ? () => {
                              widget.toggleTimer(
                                ref,
                                showNotification('title', 'body'),
                                widget.timer,
                                widget.toggle,
                              ),
                              widget.setToggle(),
                            }
                        : null,
                    label: Text('Start'),
                    icon: Icon(Icons.launch),
                    backgroundColor: Colors.greenAccent,
                  ),
                ),
              ),
              Expanded(
                child: Center(
                  child: FloatingActionButton.extended(
                    heroTag: 'btn2',
                    onPressed: widget.toggle
                        ? () => {
                              widget.toggleTimer(
                                ref,
                                showNotification('title', 'body'),
                                widget.timer,
                                widget.toggle,
                              ),
                              widget.setToggle(),
                            }
                        : null,
                    label: Text('Stop'),
                    icon: Icon(Icons.stop),
                    backgroundColor: Colors.pink,
                  ),
                ),
              ),
            ],
          ),

StrategyProvider.dart :

class StrategyProvider with ChangeNotifier {
  // Toggles
  bool _companyStratOneToggle = false;
  bool get companyStratOneToggle => _companyStratOneToggle;
  ChartLiveData _companyChartLiveData = ChartLiveData(
    ...
  );

  ChartLiveData get companyChartLiveData => _companyChartLiveData;
  
  toggleCompanyTimer(ref, showNotification, timer, bool toggle) {
    if (toggle == false) {
      timer = Timer.periodic(
        Duration(seconds: 5),
        (Timer t) => {
          fetchCompanyLiveStockData( // The function I want to run every 5 seconds
            ref,
            showNotification,
          ),
        },
      );
    } else {
      timer.cancel();
      print("Timer Canceled!");
    }
  }

  // Toggle setters for different strategies
  setCompanyStratOneToggle() {
    _companyStratOneToggle = !_companyStratOneToggle;
    notifyListeners();
  }
}

So as I told earlier that I am able to start the timer but cannot cancel it (as it is null) and it keeps on running every 5 seconds. Below is the console output:

Unhandled Exception: NoSuchMethodError: Class 'Future<dynamic>' has no instance method 'call'.
E/flutter ( 2746): Receiver: Instance of 'Future<dynamic>'
E/flutter ( 2746): <asynchronous suspension>
E/flutter ( 2746):
I/flutter ( 2746): Timer: null

And when I press cancel button:

The following NoSuchMethodError was thrown while handling a gesture:
The method 'cancel' was called on null.
Receiver: null
Tried calling: cancel()

When the exception was thrown, this was the stack
#0      Object.noSuchMethod (dart:core-patch/object_patch.dart:63:5)
#1      StrategyProvider.toggleSbinTimer
package:myapp/…/global/strategy_provider.dart:67
#2      _StrategyScreenState.build.<anonymous closure>
package:myapp/…/global/strategy_screen.dart:152

CodePudding user response:

Ultimately your issue is that you're trying to construct a Timer object in toggleCompanyTimer and trying to assign a reference to that object in the caller. However, Dart is not pass-by-reference, so there is no direct way for toggleCompanyTimer to do that.

You'd be much better off if your StrategyProvider class owned and managed the Timer object entirely by itself. For example:

class StrategyProvider with ChangeNotifier {
  Timer? timer;

  // ... Other code...

  void toggleCompanyTimer(ref, showNotification, bool stopTimer) {
    // Cancel any existing Timer before creating a new one.
    timer?.cancel();
    timer = null;

    if (!stopTimer) {
      timer = Timer.periodic(
        Duration(seconds: 5),
        (Timer t) => {
          fetchCompanyLiveStockData(
            ref,
            showNotification,
          ),
        },
      );
    } else {
      print("Timer Canceled!");
    }
  }
}
  • Related