Reputation: 2081
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),
),
),
);
}
}
Upvotes: 146
Views: 376240
Reputation: 15699
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 :
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 :
Timer.periodic
creation with a non null condition so that clicking the button multiple times has no effectif (_timer != null) {
_timer = new Timer.periodic(...);
}
if (_timer != null) {
_timer.cancel();
_start = 10;
}
_timer = new Timer.periodic(...);
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
Upvotes: 310
Reputation: 177
import 'package:async/async.dart';
late CancelableOperation? cancellableOperation;
int counter = 5;
StatefulBuilder(
builder: (context, setState) {
if (counter > 0) {
cancellableOperation = CancelableOperation.fromFuture(
Future.delayed(const Duration(seconds: 1)),
onCancel: () => {},
);
cancellableOperation?.value.whenComplete(() => setState(() => counter--));
return buildButton(
text: 'Wait($counter)',
textColor: getTextColor,
backgroundColor: Colors.grey.withOpacity(0.5),
onPressed: () {},
);
}
return buildButton(
text: 'Complete',
textColor: Colors.green,
backgroundColor: Colors.greenAccent.withOpacity(0.5),
onPressed: () {},
);
},
)
I did something like this for my dialog's complete button. It counts for 5 seconds before to show complete button. But do not forget to call cancellableOperation?.cancel()
if you close the dialog or page before counting complete.
Upvotes: 0
Reputation: 5986
I've Created a amazing timer without any plugin, here you can also get count down timer. And don't forget to stop the timer on back pressed.
Here is the link of my timer full Project. *Hope this will help someone. Thank you. *
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: AttendanceScreen(),
);
}
}
class AttendanceScreen extends StatefulWidget {
AttendanceScreen();
@override
_AttendanceScreenState createState() => _AttendanceScreenState();
}
class _AttendanceScreenState extends State<AttendanceScreen> {
static var countdownDuration = Duration(minutes: 10);
static var countdownDuration1 = Duration(minutes: 10);
Duration duration = Duration();
Duration duration1 = Duration();
Timer? timer;
Timer? timer1;
bool countDown = true;
bool countDown1 = true;
@override
void initState() {
var hours;
var mints;
var secs;
hours = int.parse("00");
mints = int.parse("00");
secs = int.parse("00");
countdownDuration = Duration(hours: hours, minutes: mints, seconds: secs);
startTimer();
reset();
var hours1;
var mints1;
var secs1;
hours1 = int.parse("10");
mints1 = int.parse("00");
secs1 = int.parse("00");
countdownDuration1 =
Duration(hours: hours1, minutes: mints1, seconds: secs1);
startTimer1();
reset1();
super.initState();
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: _onWillPop,
child: Scaffold(
appBar: AppBar(
title: Text("Timer Example"),
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
color: Colors.white,
onPressed: () {
_onWillPop();
},
),
),
body: Container(
color: Colors.black12,
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
height: 20,
),
Text(
"Timer",
style: TextStyle(fontSize: 25),
),
Container(
margin: EdgeInsets.only(top: 30, bottom: 30),
child: buildTime()),
SizedBox(
height: 20,
),
Text(
"Count down timer",
style: TextStyle(fontSize: 25),
),
Container(
margin: EdgeInsets.only(top: 30, bottom: 30),
child: buildTime1()),
]),
),
),
);
}
Future<bool> _onWillPop() async {
final isRunning = timer == null ? false : timer!.isActive;
if (isRunning) {
timer!.cancel();
}
Navigator.of(context, rootNavigator: true).pop(context);
return true;
}
void reset() {
if (countDown) {
setState(() => duration = countdownDuration);
} else {
setState(() => duration = Duration());
}
}
void reset1() {
if (countDown) {
setState(() => duration1 = countdownDuration1);
} else {
setState(() => duration1 = Duration());
}
}
void startTimer() {
timer = Timer.periodic(Duration(seconds: 1), (_) => addTime());
}
void startTimer1() {
timer = Timer.periodic(Duration(seconds: 1), (_) => addTime1());
}
void addTime() {
final addSeconds = 1;
setState(() {
final seconds = duration.inSeconds + addSeconds;
if (seconds < 0) {
timer?.cancel();
} else {
duration = Duration(seconds: seconds);
}
});
}
void addTime1() {
final addSeconds = 1;
setState(() {
final seconds = duration1.inSeconds - addSeconds;
if (seconds < 0) {
timer1?.cancel();
} else {
duration1 = Duration(seconds: seconds);
}
});
}
Widget buildTime() {
String twoDigits(int n) => n.toString().padLeft(2, '0');
final hours = twoDigits(duration.inHours);
final minutes = twoDigits(duration.inMinutes.remainder(60));
final seconds = twoDigits(duration.inSeconds.remainder(60));
return Row(mainAxisAlignment: MainAxisAlignment.center, children: [
buildTimeCard(time: hours, header: 'HOURS'),
SizedBox(
width: 8,
),
buildTimeCard(time: minutes, header: 'MINUTES'),
SizedBox(
width: 8,
),
buildTimeCard(time: seconds, header: 'SECONDS'),
]);
}
Widget buildTime1() {
String twoDigits(int n) => n.toString().padLeft(2, '0');
final hours = twoDigits(duration1.inHours);
final minutes = twoDigits(duration1.inMinutes.remainder(60));
final seconds = twoDigits(duration1.inSeconds.remainder(60));
return Row(mainAxisAlignment: MainAxisAlignment.center, children: [
buildTimeCard(time: hours, header: 'HOURS'),
SizedBox(
width: 8,
),
buildTimeCard(time: minutes, header: 'MINUTES'),
SizedBox(
width: 8,
),
buildTimeCard(time: seconds, header: 'SECONDS'),
]);
}
Widget buildTimeCard({required String time, required String header}) =>
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white, borderRadius: BorderRadius.circular(20)),
child: Text(
time,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
fontSize: 50),
),
),
SizedBox(
height: 24,
),
Text(header, style: TextStyle(color: Colors.black45)),
],
);
}
Upvotes: 15
Reputation: 3049
Many answer already provided. I suggest a shortcut way-
Use this package Custom_timer
Add this to your package's pubspec.yaml file:
dependencies:
custom_timer: ^0.0.3
(use latest version)
and very simple to implement
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: Text("CustomTimer example"),
),
body: Center(
child: CustomTimer(
from: Duration(hours: 12),
to: Duration(hours: 0),
onBuildAction: CustomTimerAction.auto_start,
builder: (CustomTimerRemainingTime remaining) {
return Text(
"${remaining.hours}:${remaining.minutes}:${remaining.seconds}",
style: TextStyle(fontSize: 30.0),
);
},
),
),
),
);
}
Upvotes: 1
Reputation: 41
import 'package:rxdart/rxdart.dart';
final BehaviorSubject<int> resendTimeController = BehaviorSubject<int>();
static const timerDuration = 90;
int resendTimer = 0;
Timer? timer;
void startTimer() {
timer?.cancel();
resendTimer = timerDuration;
timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (resendTimer > 0) {
resendTimer--;
resendTimeController.add(resendTimer);
if (resendTimer == 0) {
timer.cancel();
}
}
});
}
and use intl this
StreamBuilder<int>(
stream: _bloc.resendTimeController,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Container();
}
final DateTime date = DateTime.fromMillisecondsSinceEpoch(snapshot.data! * 1000);
return GestureDetector(
onTap: () {
if (snapshot.data == 0) {
_bloc.resendCode();
}
},
child: Text(
snapshot.data! > 0 ? 'Resend code ${DateFormat('mm:ss').format(date)}' : 'Resend SMS',
),
);
},
),
Upvotes: 0
Reputation: 24120
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
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,
);
},
),
);
}
}
Upvotes: 52
Reputation: 20221
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
Upvotes: 43
Reputation: 3485
For showing your total seconds in this format hh:mm:ss, you can use below method:
String getDuration(int totalSeconds) {
String seconds = (totalSeconds % 60).toInt().toString().padLeft(2, '0');
String minutes =
((totalSeconds / 60) % 60).toInt().toString().padLeft(2, '0');
String hours = (totalSeconds ~/ 3600).toString().padLeft(2, '0');
return "$hours\:$minutes\:$seconds";
}
Upvotes: 3
Reputation: 1714
import 'dart:async';
import 'package:flutter/material.dart';
class CustomTimer extends StatefulWidget {
@override
_CustomTimerState createState() => _CustomTimerState();
}
class _CustomTimerState extends State<CustomTimer> {
final _maxSeconds = 61;
int _currentSecond = 0;
Timer _timer;
String get _timerText {
final secondsPerMinute = 60;
final secondsLeft = _maxSeconds - _currentSecond;
final formattedMinutesLeft =
(secondsLeft ~/ secondsPerMinute).toString().padLeft(2, '0');
final formattedSecondsLeft =
(secondsLeft % secondsPerMinute).toString().padLeft(2, '0');
print('$formattedMinutesLeft : $formattedSecondsLeft');
return '$formattedMinutesLeft : $formattedSecondsLeft';
}
@override
void initState() {
super.initState();
_startTimer();
}
@override
void dispose() {
_timer.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.timer),
Text(_timerText),
],
),
),
);
}
void _startTimer() {
final duration = Duration(seconds: 1);
_timer = Timer.periodic(duration, (Timer timer) {
setState(() {
_currentSecond = timer.tick;
if (timer.tick >= _maxSeconds) timer.cancel();
});
});
}
}
Upvotes: 4
Reputation: 2291
I'm using https://pub.dev/packages/flutter_countdown_timer
dependencies: flutter_countdown_timer: ^1.0.0
$ flutter pub get
CountdownTimer(endTime: 1594829147719)
1594829147719 is your timestamp in milliseconds
Upvotes: 3
Reputation: 3079
Here is my Timer widget, not related to the Question but may help someone.
import 'dart:async';
import 'package:flutter/material.dart';
class OtpTimer extends StatefulWidget {
@override
_OtpTimerState createState() => _OtpTimerState();
}
class _OtpTimerState extends State<OtpTimer> {
final interval = const Duration(seconds: 1);
final int timerMaxSeconds = 60;
int currentSeconds = 0;
String get timerText =>
'${((timerMaxSeconds - currentSeconds) ~/ 60).toString().padLeft(2, '0')}: ${((timerMaxSeconds - currentSeconds) % 60).toString().padLeft(2, '0')}';
startTimeout([int milliseconds]) {
var duration = interval;
Timer.periodic(duration, (timer) {
setState(() {
print(timer.tick);
currentSeconds = timer.tick;
if (timer.tick >= timerMaxSeconds) timer.cancel();
});
});
}
@override
void initState() {
startTimeout();
super.initState();
}
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(Icons.timer),
SizedBox(
width: 5,
),
Text(timerText)
],
);
}
}
You will get something like this
Upvotes: 24
Reputation: 73
If all you need is a simple countdown timer, this is a good alternative instead of installing a package. Happy coding!
countDownTimer() async {
int timerCount;
for (int x = 5; x > 0; x--) {
await Future.delayed(Duration(seconds: 1)).then((_) {
setState(() {
timerCount -= 1;
});
});
}
}
Upvotes: 7
Reputation: 1282
doesnt directly answer your question. But helpful for those who want to start something after some time.
Future.delayed(Duration(seconds: 1), () {
print('yo hey');
});
Upvotes: 20
Reputation: 5783
Countdown timer in one line
CountdownTimer(Duration(seconds: 5), Duration(seconds: 1)).listen((data){
})..onData((data){
print('data $data');
})..onDone((){
print('onDone.........');
});
Upvotes: -3