Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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),
        ),
      ),
    );
  }
}
like image 416
Floris Marpepa Avatar asked Feb 09 '19 19:02

Floris Marpepa


People also ask

How to use timer in flutter?

Flutter has a Timer class that we can configure. As a result, we can either start it, stop it or resume it. When a timer in flutter works it actually starts a countdown. As we know, in many cases, we need a timer to start a countdown in a flutter app. We can see a simple code of Timer class in flutter.

What is countup and countdown timer?

Simple CountUp timer / CountDown timer. It easily create app of stopwatch. Simple CountDown timer. Using for create a simple timer. It's pure all A widget that rebuilds itself on scheduled, periodic, or dynamically generated time events.

How to build a count down timer with the animation?

In this article, I will be building a count down timer with the help of the animation and using custom paint. First of all, create an app which returns the MaterialApp. Now create a new Widget CountDownTimer and make sure that it must be a Stateful Widget because we are using animation and we will need to add TickerProviderStateMixen .

What is app write in flutter?

Appwrite is an Open Source Firebase alternative designed to provide Flutter developers with all the tools they need to build amazing apps! Click here to learn more. Make an animated circular countdown using Circular Countdown Timer. Simple CountUp timer / CountDown timer.


3 Answers

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

like image 95
Yann39 Avatar answered Oct 19 '22 08:10

Yann39


I have created a Generic Timer Widget which can be used to display any kind of timer and its flexible as well.

This Widget takes following properties

  1. secondsRemaining: duration for which timer needs to run in seconds
  2. whenTimeExpires: what action needs to be performed if timer finished
  3. countDownStyle: any kind of style which you want to give to timer
  4. countDownFormatter: the way user wants to display the count down timer e.g hh mm ss string like 01 hours: 20 minutes: 45 seconds

you can provide a default formatter ( formatHHMMSS ) in case you don't want to supply it from every place.

// provide implementation for this - formatHHMMSS(duration.inSeconds); or use below one which I have provided.

import 'package:flutter/material.dart';
class CountDownTimer extends StatefulWidget {
  const CountDownTimer({
    Key key,
    int secondsRemaining,
    this.countDownTimerStyle,
    this.whenTimeExpires,
    this.countDownFormatter,
  })  : secondsRemaining = secondsRemaining,
        super(key: key);

  final int secondsRemaining;
  final Function whenTimeExpires;
  final Function countDownFormatter;
  final TextStyle countDownTimerStyle;

  State createState() => new _CountDownTimerState();
}

class _CountDownTimerState extends State<CountDownTimer>
    with TickerProviderStateMixin {
  AnimationController _controller;
  Duration duration;

  String get timerDisplayString {
    Duration duration = _controller.duration * _controller.value;
    return widget.countDownFormatter != null
        ? widget.countDownFormatter(duration.inSeconds)
        : formatHHMMSS(duration.inSeconds);
      // In case user doesn't provide formatter use the default one
     // for that create a method which will be called formatHHMMSS or whatever you like
  }

  @override
  void initState() {
    super.initState();
    duration = new Duration(seconds: widget.secondsRemaining);
    _controller = new AnimationController(
      vsync: this,
      duration: duration,
    );
    _controller.reverse(from: widget.secondsRemaining.toDouble());
    _controller.addStatusListener((status) {
      if (status == AnimationStatus.completed || status == AnimationStatus.dismissed) {
        widget.whenTimeExpires();
      }
    });
  }

  @override
  void didUpdateWidget(CountDownTimer oldWidget) {
    if (widget.secondsRemaining != oldWidget.secondsRemaining) {
      setState(() {
        duration = new Duration(seconds: widget.secondsRemaining);
        _controller.dispose();
        _controller = new AnimationController(
          vsync: this,
          duration: duration,
        );
        _controller.reverse(from: widget.secondsRemaining.toDouble());
        _controller.addStatusListener((status) {
          if (status == AnimationStatus.completed) {
            widget.whenTimeExpires();
          } else if (status == AnimationStatus.dismissed) {
            print("Animation Complete");
          }
        });
      });
    }
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return new Center(
        child: AnimatedBuilder(
            animation: _controller,
            builder: (_, Widget child) {
              return Text(
                timerDisplayString,
                style: widget.countDownTimerStyle,
              );
            }));
  }
}

Usage:

 Container(
       width: 60.0,
       padding: EdgeInsets.only(top: 3.0, right: 4.0),
         child: CountDownTimer(
           secondsRemaining: 30,
           whenTimeExpires: () {
              setState(() {
                hasTimerStopped = true;
              });
            },
            countDownTimerStyle: TextStyle(
                color: Color(0XFFf5a623),
                fontSize: 17.0,
                height: 1.2,
            ),
          ),
        )

example for formatHHMMSS:

String formatHHMMSS(int seconds) {
  int hours = (seconds / 3600).truncate();
  seconds = (seconds % 3600).truncate();
  int minutes = (seconds / 60).truncate();

  String hoursStr = (hours).toString().padLeft(2, '0');
  String minutesStr = (minutes).toString().padLeft(2, '0');
  String secondsStr = (seconds % 60).toString().padLeft(2, '0');

  if (hours == 0) {
    return "$minutesStr:$secondsStr";
  }

  return "$hoursStr:$minutesStr:$secondsStr";
}

Null Safe Version of the Above Code

import 'package:flutter/material.dart';

class CountDownTimer extends StatefulWidget {
  const CountDownTimer({
    Key? key,
    required this.secondsRemaining,
    required this.whenTimeExpires,
    this.countDownFormatter,
    this.countDownTimerStyle,
  }) : super(key: key);

  final int secondsRemaining;
  final VoidCallback whenTimeExpires;
  final TextStyle? countDownTimerStyle;
  final Function(int seconds)? countDownFormatter;

  @override
  State createState() => _CountDownTimerState();
}

class _CountDownTimerState extends State<CountDownTimer>
    with TickerProviderStateMixin {
  late final AnimationController _controller;
  late final Duration duration;

  String get timerDisplayString {
    final duration = _controller.duration! * _controller.value;
    if (widget.countDownFormatter != null) {
      return widget.countDownFormatter!(duration.inSeconds) as String;
    } else {
      return formatHHMMSS(duration.inSeconds);
    }
  }

  String formatHHMMSS(int seconds) {
    final hours = (seconds / 3600).truncate();
    seconds = (seconds % 3600).truncate();
    final minutes = (seconds / 60).truncate();

    final hoursStr = (hours).toString().padLeft(2, '0');
    final minutesStr = (minutes).toString().padLeft(2, '0');
    final secondsStr = (seconds % 60).toString().padLeft(2, '0');

    if (hours == 0) {
      return '$minutesStr:$secondsStr';
    }

    return '$hoursStr:$minutesStr:$secondsStr';
  }

  @override
  void initState() {
    super.initState();
    duration = Duration(seconds: widget.secondsRemaining);
    _controller = AnimationController(
      vsync: this,
      duration: duration,
    );
    _controller
      ..reverse(from: widget.secondsRemaining.toDouble())
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed ||
            status == AnimationStatus.dismissed) {
          widget.whenTimeExpires();
        }
      });
  }

  @override
  void didUpdateWidget(CountDownTimer oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.secondsRemaining != oldWidget.secondsRemaining) {
      setState(() {
        duration = Duration(seconds: widget.secondsRemaining);
        _controller.dispose();
        _controller = AnimationController(
          vsync: this,
          duration: duration,
        );
        _controller
          ..reverse(from: widget.secondsRemaining.toDouble())
          ..addStatusListener((status) {
            if (status == AnimationStatus.completed) {
              widget.whenTimeExpires();
            }
          });
      });
    }
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: AnimatedBuilder(
        animation: _controller,
        builder: (_, Widget? child) {
          return Text(
            timerDisplayString,
            style: widget.countDownTimerStyle,
          );
        },
      ),
    );
  }
}

like image 32
WitVault Avatar answered Oct 19 '22 08:10

WitVault


Little late to the party but why don't you guys try animation.No I am not telling you to manage animation controllers and disposing them off and all that stuff, there's a built-in widget for that called TweenAnimationBuilder. You can animate between values of any type, here's an example with a Duration class

TweenAnimationBuilder<Duration>(
  duration: Duration(minutes: 3),
  tween: Tween(begin: Duration(minutes: 3), end: Duration.zero),
  onEnd: () {
    print('Timer ended');
  },
  builder: (BuildContext context, Duration value, Widget? child) {
    final minutes = value.inMinutes;
    final seconds = value.inSeconds % 60;
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 5),
      child: Text('$minutes:$seconds',
               textAlign: TextAlign.center,
               style: TextStyle(
               color: Colors.black,
               fontWeight: FontWeight.bold,
               fontSize: 30)));
    }),

and You also get onEnd call back which notifies you when the animation completes;

here's the output

like image 25
Mahesh Jamdade Avatar answered Oct 19 '22 07:10

Mahesh Jamdade