How to add timer in tasks after being triggered in flutter - flutter

I am working on a slider in flutter which is focused on rating. I want to add a timer to manage a rating time (i.e if the user rated then he/she can't rate again for an hour. Which means once the function is triggered it won't trigger again for a specific time).
So how is it possible to do that. I have a little knowledge about the Timer class and I don't how exactly it should be done. By doing this we are bounding the user not to rate as many times as user want

A simple implementation using a Timer for the functionality you require is to store a bool and start the timer and toggle the bool value when the user performs the function and on timeout toggle the `bool to false and start the timer again.
Below is an example that uses setState.
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(home:TimerExample()));
}
class TimerExample extends StatefulWidget{
#override
State<StatefulWidget> createState() {
return _TimerExampleState();
}
}
class _TimerExampleState extends State<TimerExample> {
// Using a short timer for testing, set a duration
// you can set a any duration you like, For Example:
// Duration(hours: 1) | Duration(minutes: 30)
final _timeoutDuration = Duration(seconds:3);
late Timer _timer;
bool _canVote = true;
#override
void dispose() {
super.dispose();
_timer.cancel();
}
void onTimeout() {
setState(() => _canVote = true);
}
void startTimer() {
setState(() {
_canVote = false;
_timer = Timer(_timeoutDuration, onTimeout);
});
}
#override
Widget build(BuildContext context) {
return TextButton(
onPressed: _canVote ? startTimer : null,
child: Text('Vote')
);
}
}

Related

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();
}
}

Need Help updating chart data in flutter at every x amount of seconds

So I'm using the Sparkline library for flutter to create a line chart and it works when I use a static list Eg. ([0, 10, 20, 20, 30]). But I want to make it so that at every say 10 seconds it would add a value, for now that could be anything but later i want to pull that value from firebase.
I've looked at other examples of people trying to run a function multiple time init state but it isnt working. I know I need to redraw the widget but I don't think I'm doing it right or I'm missing something.
class BikeState extends State<BikeScreen> {
Timer timer;
List<double> speedList = new List();
Sparkline speedChart1 = Sparkline(data: [0],);
void updateChart(Timer timer){
speedChart1 = Sparkline(data: speedList,);
speedList.add(10);
speedChart1 = Sparkline(data: speedList,);
}
#override
void initState() {
super.initState();
timer = Timer.periodic(Duration(seconds: 15), updateChart);
}
#override
void dispose() {
timer?.cancel();
super.dispose();
}
All that happens when i run it is that I get the just the graph which has the values passed into it when it was declared and nothing was changed.
Flutter will not repaint or rebuild any widgets for you on its own. In your case you already have a stateful widget, which is the correct way, but you also need to tell flutter that your state has changed. This is done by using the setState method like this:
void updateChart(Timer timer) {
setState(() {
speedChart1 = Sparkline(data: speedList,);
speedList.add(10);
speedChart1 = Sparkline(data: speedList,);
});
}
Additionally, depending on how your build method looks, I think you should remove the Sparkline Widget from your state and have it purely during build like this:
class BikeState extends State<BikeScreen> {
Timer timer;
List<double> speedList = new List();
void updateChart(Timer timer){
setState(() {
speedList.add(10);
});
}
#override
void initState() {
super.initState();
timer = Timer.periodic(Duration(seconds: 15), updateChart);
}
#override
void dipose() {
timer?.dipose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Sparkline(data: speedList);
}
}

Async/Await not working as expect. How can I start a timer exactly 4 seconds after a sound has started playing in Flutter?

When my app's game screen appears, an audio file starts playing that says "Ready? 3...2...1...GO" and then plays music.
I'd like the game timer to start at the same time that it says "GO", which is exactly 4 seconds into the audio.
I got pretty close by starting the audio file in initState() and then using "Future.delayed(const Duration(seconds: 4), () => startTimer());" but the problem is that the first time a user plays, it take a bit of time for the audio to load, so then the timer starts before the audio says "GO".
I tried to solve this by making an startGameSequence() function that uses async and await, but it isn't working. What am I doing wrong?
Here's what I've got so far:
import 'package:audioplayers/audio_cache.dart';
import 'dart:async';
class GameScreen extends StatefulWidget {
#override
_GameScreenState createState() => _GameScreenState();
}
class _GameScreenState extends State<GameScreen> {
void loadAudio() {
final player = AudioCache();
player.load('audio/ready-321-go-music.mp3');
}
void playAudio() {
final player = AudioCache();
player.play('audio/ready-321-go-music.mp3');
}
void startGameSequence() async {
await loadAudio();
playAudio();
// After audio finishes loading / starts playing, THEN I'd like the 4-second delayed timer to start. (Currently, it seems that the time starts too early because the audio takes a bit to load.)
Future.delayed(
const Duration(seconds: 4),
() => startTimer(),
);
}
Timer _timer;
double _timeRemaining = 7.00;
void startTimer() {
const tick = const Duration(milliseconds: 10);
_timer = new Timer.periodic(
tick,
(Timer timer) => setState(
() {
if (_timeRemaining < 0.01) {
timer.cancel();
} else {
_timeRemaining = _timeRemaining - 0.01;
}
},
),
);
}
#override
initState() {
super.initState();
startGameSequence();
}
Thank you in advance for any help!
InitState is not async, wherefore this not working correctly.
A one solution is, load the audioFile in initState() and execute startGameSequence() in didUpdateWidget() function without the Future.delayed() .
#override
initState() {
super.initState();
await loadAudio();
}
#override
void didUpdateWidget(GameScreen oldWidget) {
startGameSequence()
super.didUpdateWidget(oldWidget);
}
void startGameSequence() {
playAudio();
}
This function execute just when the first layout appears on the screen.

Listening to a variable change in flutter

I'm trying to listen to a variable change to execute some code. So the variable is a bool named reset. I want to execute something (say reset the animation controller) once the animation ends OR a button (from another widget) is pressed. Executing something when the animation ends works as once it ends AnimationStatus.dismissed will be its state and the listener will be called. This then allows me to use a callback function onCountdownexpire in order to set the variable reset accordingly and based on what it is set, execute some code in the if(widget.reset) block. So there is no need to listen for this case.
Problem:
However, lets say a button is pressed (in another widget) and I set the variable reset to true. I want the animation to stop and reset. I cannot now depend on the AnimationStatus listener as it only executes when there is a state change. I want it to reset during its state. So I have to somehow listen to the variable widget.reset.
I have done some research on this and found out that ValueNotifier might be a way to go but there are not a lot of examples on how to go about using it. Like how do I go about listening to it ?
Code:
class Countdown extends StatefulWidget {
final VoidCallback onCountdownExpire;
bool reset;
Countdown(this.onCountdownExpire);
#override
CountdownState createState() => CountdownState();
}
class CountdownState extends State<Countdown> with TickerProviderStateMixin {
AnimationController controller;
String get timerString {
Duration duration = controller.duration * controller.value;
return '${duration.inMinutes}:${(duration.inSeconds % 60).toString()}';
}
#override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: Duration(seconds: 2),
)..addStatusListener((AnimationStatus status){
if (status == AnimationStatus.dismissed) {
debugPrint("Animation.dismissed");
widget.onCountdownExpire();
if (widget.reset) {
widget.reset = false;
controller.reverse(from: 1.0);
}
}
});
controller.reverse(from: 1.0);
}
... // omitted code
}
What I have tried but it does not seem to be working as expected:
class Countdown extends StatefulWidget {
final VoidCallback onCountdownExpire;
Countdown(this.onCountdownExpire);
ValueNotifier reset = ValueNotifier(false);
#override
CountdownState createState() => CountdownState();
}
class CountdownState extends State<Countdown> with TickerProviderStateMixin {
AnimationController controller;
#override
void initState() {
widget.reset.addListener(() {
debugPrint("value notifier is true");
controller.reverse(from: 1.0);
});
super.initState();
controller = AnimationController(
vsync: this,
duration: Duration(seconds: 2),
)..addStatusListener((AnimationStatus status){
if (status == AnimationStatus.dismissed) {
debugPrint("Animation.dismissed");
widget.onCountdownExpire();
}
});
controller.reverse(from: 1.0);
}
... // omitted code
}
Update: The solution (above) was actually working, I just had to use something like this to notify the listener:
countdown.reset.notifyListeners();
// OR
countdown.reset.value = true;
OP already got an answer in the comments. I'm just typing it here so the question is correctly marked as answered.
Just notify the listener:
countdown.reset.notifyListeners();
// OR
countdown.reset.value = true;
Credit to #pskink
Use of overwriting getter/setters
You can make use of the getter and setter functions.
In the example below we are printing happy birthday when the person's age changes:
class Person {
int _age;
Person(this._age);
int get age => _age;
set age(int newValue) {
print("Happy birthday! You have a new Age.");
//do some awsome things here when the age changes
_age = newValue;
}
}
The usage is exactly as you are used to:
final newPerson = Person(20);
print(newPerson.age); //20
newPerson.age = 21; //prints happy birthday
print(newPerson.age); //21

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}');
}
}