How to make a count down timer in Flutter - flutter

I'm looking for the best way to make a countdown timer in Flutter.
I made it this way, but i'm not sure this efficient way to do it, this is my code :
class CountDown extends StatefulWidget {
final int secondsNum;
CountDown({Key key, #required this.secondsNum}) : super(key: key);
#override
_CountDownState createState() => _CountDownState(secondsNum);
}
class _CountDownState extends State<CountDown> {
Timer _timer;
int _start;
_CountDownState(int start) {
this._start = start;
startTimer();
}
void startTimer() {
const oneSec = const Duration(seconds: 1);
_timer = new Timer.periodic(
oneSec,
(Timer timer) => setState(() {
if (_start < 1) {
timer.cancel();
} else {
_start = _start - 1;
}
})
);
}
#override
Widget build(BuildContext context) {
return Text("$_start");
}
#override
void dispose() {
_timer.cancel();
super.dispose();
}
}
And then import that file and call it this way: CountDown(secondsNum: 50).
The problem is that I'm going to call CountDown(secondsNum: 50) like 10 times in the same screen.
There are a lot of Javascript libraries that can do the same thing for Web, but I didn't find any for Flutter so far. If anyone has an idea about the best way to do this, please post it below. Thanks in advance.

Related

How to Access Custom Hook functions from another class

i have a custom hook timer_hook.dart
The way this hooks works is whenever the user open's up the page, the counter begins to count down, so i am trying to access the method "restartTimer()"
from the main class where i am using the hook. but i dont know how to achieve that, sorry i am still learning flutter. and i don't want to use the stateful widget.
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
Duration useCountDownTimer() {
return use(const CountdownTimer());
}
class CountdownTimer extends Hook<Duration> {
const CountdownTimer();
#override
_CountdownTimerState createState() => _CountdownTimerState();
}
class _CountdownTimerState extends HookState<Duration, CountdownTimer> {
Timer? _timer;
Duration _duration = const Duration(seconds: 6);
void countDownTime() {
const reduceSeconds = 1;
setState(() {
final seconds = _duration.inSeconds - reduceSeconds;
_duration = Duration(seconds: seconds);
if (_duration.inSeconds == 0) {
_timer?.cancel();
}
});
}
void restartTimer() {
setState(() {
_duration = const Duration(seconds: 90);
_timer =
Timer.periodic(const Duration(seconds: 1), (_) => countDownTime());
});
}
#override
void initHook() {
super.initHook();
_timer = Timer.periodic(const Duration(seconds: 1), (_) => countDownTime());
}
#override
Duration build(BuildContext context) {
return _duration;
}
#override
void dispose() {
_timer?.cancel();
super.dispose();
}
}

How to display the time elapsed from a timestamp?

To be more precise, I would like to display the number of minutes passed since a timestamp fetched from an API. Should Stopwatch or Timer be used, or something else?
I'd suggest something like the below. You can update the duration associated with the Timer to define how often it rebuilds the widget.
I've also left it up to you to decide how you would like to format the duration.
class ElapsedTime extends StatefulWidget {
final String timestamp;
const ElapsedTime({
Key key,
#required this.timestamp,
}) : super(key: key);
#override
_ElapsedTimeState createState() => _ElapsedTimeState();
}
class _ElapsedTimeState extends State<ElapsedTime> {
Timer _timer;
DateTime _initialTime;
String _currentDuration;
#override
void didUpdateWidget(ElapsedTime oldWidget) {
super.didUpdateWidget(oldWidget);
if(widget.timestamp != oldWidget.timestamp) {
_initialTime = _parseTimestamp();
_currentDuration = _formatDuration(_calcElapsedTime());
}
}
#override
void initState() {
super.initState();
_initialTime = _parseTimestamp();
_currentDuration = _formatDuration(_calcElapsedTime());
_timer = Timer.periodic(const Duration(seconds: 1), (Timer t) {
setState(() {
_currentDuration = _formatDuration(_calcElapsedTime());
});
});
}
Duration _calcElapsedTime() => _initialTime.difference(DateTime.now());
DateTime _parseTimestamp() => DateTime.parse(widget.timestamp);
// TODO update this to fit your own needs
String _formatDuration(final Duration duration) => duration.toString();
#override
void dispose() {
_timer?.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Text(_currentDuration);
}
}
In your case, you should first parse your timestamp to a DateTime object. Then you can retrieve the time delta between the fetched DateTime and the current time by using the different method of the DateTime class.
final Duration myDuration = DateTime.parse(myTimeStamp).difference(DateTime.now));
For more information, please visit https://api.dart.dev/stable/2.10.4/dart-core/DateTime/difference.html.
Timer and Stopwatch are used for async work; you do not need such complexity level in your case.
Another solution, based on JayDev's answer, has a property that represents the number of milliseconds since the "Unix epoch", e.g. DateTime.now().millisecondsSinceEpoch corresponds to the current number of milliseconds passed since 1st of January 1970.
In the initialize() method a timer initialization is delayed by the number of seconds passed since the whole minute, otherwise, it would initially wait for 60 seconds to increment a counter.
This widget only shows the number of minutes since a timestamp, the same logic can be applied to show more information.
class ElapsedTime extends StatefulWidget {
final int timestamp;
const _lapsedTime({Key key, this.timestamp}) : super(key: key);
#override
__ElapsedTimeState createState() => _ElapsedTimeState();
}
class _ElapsedTimeState extends State<ElapsedTime> {
Timer _timer;
int counter; // represents the number of minutes
#override
void didUpdateWidget(ElapsedTime oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.timestamp != oldWidget.timestamp) initialize();
}
#override
void initState() {
super.initState();
initialize();
}
void initialize() {
counter = calculateElapsedMinutes();
Future.delayed(Duration(milliseconds: widget.timestamp % 60000))
.then((_) => setupTimerAndIncrement());
}
int calculateElapsedMinutes() =>
((DateTime.now().millisecondsSinceEpoch - widget.timestamp) / 60000)
.floor();
void setupTimerAndIncrement() {
if (!mounted) return; //to prevent calling setState() after dispose()
incrementCounter();
_timer = Timer.periodic(
const Duration(minutes: 1), (Timer t) => incrementCounter());
}
#override
Widget build(BuildContext context) => Text(counter.toString());
void incrementCounter() => setState(() => counter++);
#override
void dispose() {
_timer?.cancel();
super.dispose();
}
}

How do I change between two images on button click?

How do I create buttons (start, loop) for two images changing?
_start() that load first image then change to second image and end at first image.
_loop() that continues to loop between 2 images.
P/S: Better if we can use time like milliseconds to adjust.
Thank you.
Fallow the code below it will change the images using timer don't forget to edit the images.
class ImageLogo extends StatefulWidget {
const ImageLogo({
Key key,
}) : super(key: key);
#override
_ImageLogoState createState() => _ImageLogoState();
}
class _ImageLogoState extends State<ImageLogo> {
Timer _timer;
int _pos = 0;
List<String> photos = [
images/image1.jpg,
images/image2.jpg
];
#override
void initState() {
_timer =
Timer.periodic(Duration(seconds: 10), (Timer t) {
setState(() {
_pos = (_pos + 1) % photos.length;
});
});
super.initState();
}
#override
void dispose() {
_timer.cancel();
_timer = null;
super.dispose();
}
#override
Widget build(BuildContext context) {
return Image(
width: 12 * SizeConfig.widthMultiplier,
height: 12 * SizeConfig.heightMultiplier,
image: AssetImage(photos[_pos]),
);
}
}
on your main widget use
ImageLogo(),

Show timer progress on a CircularProgressIndicator in flutter

I'm using a RestartableTimer (subclass of Timer) as a countdown timer, to "kick people out" of a form after a certain duration.
I would like to display the progress of that timer, and I like the idea of a circular progress slowly filling up.
I'm not showing my code because I don't really have anything to show. I have a completely static progress indicator and a working timer, in a widget (stateful or stateless, whichever works best).
I face two issues and this is where I need help for :
I don't know how to check every x milliseconds for the timer progress. How can I do that? I don't need copy-pasta code, but more of "what object / which direction" should I go for?
The timer progress in ticks is not implemented (NotImplementedException) ; is there any way to have an equivalent somewhere else? That object works really well for me, except for that part.
Am I SOL or is there a way to make it?
There's nothing to be implemented in the getter tick, since RestartableTimer is not periodic. What you want is a much more complex thing, and RestartableTimer is not able to help you with that.
First, you need something to control the progress of the CircularProgressIndicator:
class ProgressController {
static const double smoothnessConstant = 250;
final Duration duration;
final Duration tickPeriod;
Timer _timer;
Timer _periodicTimer;
Stream<void> get progressStream => _progressController.stream;
StreamController<void> _progressController = StreamController<void>.broadcast();
Stream<void> get timeoutStream => _timeoutController.stream;
StreamController<void> _timeoutController = StreamController<void>.broadcast();
double get progress => _progress;
double _progress = 0;
ProgressController({#required this.duration})
: assert(duration != null),
tickPeriod = _calculateTickPeriod(duration);
void start() {
_timer = Timer(duration, () {
_cancelTimers();
_setProgressAndNotify(1);
_timeoutController.add(null);
});
_periodicTimer = Timer.periodic(
tickPeriod,
(Timer timer) {
double progress = _calculateProgress(timer);
_setProgressAndNotify(progress);
},
);
}
void restart() {
_cancelTimers();
start();
}
Future<void> dispose() async {
await _cancelStreams();
_cancelTimers();
}
double _calculateProgress(Timer timer) {
double progress = timer.tick / smoothnessConstant;
if (progress > 1) return 1;
if (progress < 0) return 0;
return progress;
}
void _setProgressAndNotify(double value) {
_progress = value;
_progressController.add(null);
}
Future<void> _cancelStreams() async {
if (!_progressController.isClosed) await _progressController.close();
if (!_timeoutController.isClosed) await _timeoutController.close();
}
void _cancelTimers() {
if (_timer?.isActive == true) _timer.cancel();
if (_periodicTimer?.isActive == true) _periodicTimer.cancel();
}
static Duration _calculateTickPeriod(Duration duration) {
double tickPeriodMs = duration.inMilliseconds / smoothnessConstant;
return Duration(milliseconds: tickPeriodMs.toInt());
}
}
Then you can implement a CircularProgressIndicator that listens to the Streams from ProgressController:
class RestartableCircularProgressIndicator extends StatefulWidget {
final ProgressController controller;
final VoidCallback onTimeout;
RestartableCircularProgressIndicator({
Key key,
#required this.controller,
this.onTimeout,
}) : assert(controller != null),
super(key: key);
#override
_RestartableCircularProgressIndicatorState createState() =>
_RestartableCircularProgressIndicatorState();
}
class _RestartableCircularProgressIndicatorState
extends State<RestartableCircularProgressIndicator> {
ProgressController get controller => widget.controller;
VoidCallback get onTimeout => widget.onTimeout;
#override
void initState() {
super.initState();
controller.progressStream.listen((_) => updateState());
controller.timeoutStream.listen((_) => onTimeout());
}
#override
Widget build(BuildContext context) {
return CircularProgressIndicator(
value: controller.progress,
);
}
void updateState() => setState(() {});
}
You can also pass some of the paramers of CircularProgressIndicator to RestartableCircularProgressIndicator, so you can customize it.
A usage example:
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
ProgressController controller;
#override
void initState() {
super.initState();
controller = ProgressController(
duration: Duration(seconds: 5),
);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
RestartableCircularProgressIndicator(
controller: controller,
onTimeout: () => print('timeout'),
),
RaisedButton(
onPressed: controller.start,
child: Text('Start'),
),
RaisedButton(
onPressed: controller.restart,
child: Text('Restart'),
),
],
),
),
),
);
}
}
I'll convert this into a library someday, but until then I cannot provide the tests and documentation to this code, so you have to study it if you want to understand what's going on here (I'm sorry...).

Is there a better way to constantly rebuild a widget?

I have widget with data that changes regularly and I'm using a Timer.periodic to rebuild the widget. This starts out working smoothly but becomes choppy pretty quickly is there a better way to do this?
class _MainScreenState extends State<MainScreen> {
static const Duration duration = Duration(milliseconds: 16);
update(){
system.updatePos(duration.inMilliseconds/1000);
setState(() {});
}
#override
Widget build(BuildContext context) {
Timer.periodic(duration, (timer){
update();
});
return PositionField(
layoutSize: widget.square,
children: system.map
);
}
}
You are making a big mistake:
The build method must never have any side effects, because it is called again whenever setState is called (or when some higher up widget changes, or when the user rotates the screen...).
Instead, you want to create your Timer in initState, and cancel it on dispose:
class TimerTest extends StatefulWidget {
#override
_TimerTestState createState() => _TimerTestState();
}
class _TimerTestState extends State<TimerTest> {
Timer _timer;
int _foo = 0;
// this is only called once when the widget is attached
#override
void initState() {
super.initState();
_timer = Timer.periodic(Duration(seconds: 1), (timer) => _update());
}
// stop the timer when the widget is detached and destroyed
#override
void dispose() {
_timer.cancel();
super.dispose();
}
void _update() {
setState(() {
_foo++;
});
}
#override
Widget build(BuildContext context) {
return Text('Foo: ${_foo}');
}
}