Search code examples
timerdartfluttercountdown

Flutter Countdown Timer


How can I do to put the value passed in the construction, to make a timer that rounds to the first decimal and shows at the child text of my RaisedButton? I've tried but without luck. I manage to make work the callback function with a simple Timer but no periodic and with no update of value in real time in the text...

import 'package:flutter/material.dart';
import 'dart:ui';
import 'dart:async';

class TimerButton extends StatefulWidget {
  final Duration timerTastoPremuto;


  TimerButton(this.timerTastoPremuto);

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

class _TimerButtonState extends State<TimerButton> {
  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.all(5.0),
      height: 135.0,
      width: 135.0,
      child: new RaisedButton(
        elevation: 100.0,
        color: Colors.white.withOpacity(.8),
        highlightElevation: 0.0,
        onPressed: () {
          int _start = widget.timerTastoPremuto.inMilliseconds;

          const oneDecimal = const Duration(milliseconds: 100);
          Timer _timer = new Timer.periodic(
              oneDecimal,
                  (Timer timer) =>
                  setState(() {
                    if (_start < 100) {
                      _timer.cancel();
                    } else {
                      _start = _start - 100;
                    }
                  }));

        },
        splashColor: Colors.red,
        highlightColor: Colors.red,
        //shape: RoundedRectangleBorder e tutto il resto uguale
        shape: BeveledRectangleBorder(
            side: BorderSide(color: Colors.black, width: 2.5),
            borderRadius: new BorderRadius.circular(15.0)),
        child: new Text(
          "$_start",
          style: new TextStyle(fontFamily: "Minim", fontSize: 50.0),
        ),
      ),
    );
  }
}

Solution

  • Here is an example using Timer.periodic :

    Countdown starts from 10 to 0 on button click :

    import 'dart:async';
    
    [...]
    
    Timer _timer;
    int _start = 10;
    
    void startTimer() {
      const oneSec = const Duration(seconds: 1);
      _timer = new Timer.periodic(
        oneSec,
        (Timer timer) {
          if (_start == 0) {
            setState(() {
              timer.cancel();
            });
          } else {
            setState(() {
              _start--;
            });
          }
        },
      );
    }
    
    @override
    void dispose() {
      _timer.cancel();
      super.dispose();
    }
    
    Widget build(BuildContext context) {
      return new Scaffold(
        appBar: AppBar(title: Text("Timer test")),
        body: Column(
          children: <Widget>[
            RaisedButton(
              onPressed: () {
                startTimer();
              },
              child: Text("start"),
            ),
            Text("$_start")
          ],
        ),
      );
    }
    

    Result :

    Flutter countdown timer example

    You can also use the CountdownTimer class from the quiver.async library, usage is even simpler :

    import 'package:quiver/async.dart';
    
    [...]
    
    int _start = 10;
    int _current = 10;
    
    void startTimer() {
      CountdownTimer countDownTimer = new CountdownTimer(
        new Duration(seconds: _start),
        new Duration(seconds: 1),
      );
    
      var sub = countDownTimer.listen(null);
      sub.onData((duration) {
        setState(() { _current = _start - duration.elapsed.inSeconds; });
      });
    
      sub.onDone(() {
        print("Done");
        sub.cancel();
      });
    }
    
    Widget build(BuildContext context) {
      return new Scaffold(
        appBar: AppBar(title: Text("Timer test")),
        body: Column(
          children: <Widget>[
            RaisedButton(
              onPressed: () {
                startTimer();
              },
              child: Text("start"),
            ),
            Text("$_current")
          ],
        ),
      );
    }
    

    EDIT : For the question in comments about button click behavior

    With the above code which uses Timer.periodic, a new timer will indeed be started on each button click, and all these timers will update the same _start variable, resulting in a faster decreasing counter.

    There are multiple solutions to change this behavior, depending on what you want to achieve :

    • disable the button once clicked so that the user could not disturb the countdown anymore (maybe enable it back once timer is cancelled)
    • wrap the Timer.periodic creation with a non null condition so that clicking the button multiple times has no effect
    if (_timer != null) {
      _timer = new Timer.periodic(...);
    }
    
    • cancel the timer and reset the countdown if you want to restart the timer on each click :
    if (_timer != null) {
      _timer.cancel();
      _start = 10;
    }
    _timer = new Timer.periodic(...);
    
    • if you want the button to act like a play/pause button :
    if (_timer != null) {
      _timer.cancel();
      _timer = null;
    } else {
      _timer = new Timer.periodic(...);
    }
    

    You could also use this official async package which provides a RestartableTimer class which extends from Timer and adds the reset method.

    So just call _timer.reset(); on each button click.

    Finally, Codepen now supports Flutter ! So here is a live example so that everyone can play with it : https://codepen.io/Yann39/pen/oNjrVOb