How can I stop Timer period in Flutter? - flutter

class _LiftButtonLayerState extends State<LiftButtonLayer> {
late Timer _timer;
late double _timeElapsed;
var isPause = false;
var isTrue = true;
#override
void initState() {
super.initState();
}
repeatDownActivate() {
_timeElapsed = 0;
final timer = Timer.periodic(Duration(milliseconds: 200), (timer) {
liftDownButton.postReq();
print("down singal 5per 1 second ");
});
}
Container(
width: 180,
height: 60,
child: GFButton(
onPressed: () {
repeatUpActivate();
print('clicked Up Button');
},
text: '▲',
textStyle: const TextStyle(
fontSize: 26,
color: GFColors.WHITE,
),
shape: GFButtonShape.pills,
size: GFSize.LARGE,
buttonBoxShadow: true,
)),
I found out that I can use Timer.cancel() from Flutter's Timer library.
I made a function for timer.cancel() , and applied timer.cancel() to onPressed on the new other button, but it doesn't work.
repeatUpActivate(isTrue) {
if (isTrue == '200') {
_timeElapsed = 0;
var _timer = Timer.periodic(Duration(milliseconds: 200), (timer) {
liftUpButton.postReq();
print("down singal 5per 1 second (ms)");
});
} else {
return false;
}
}
child: GFButton(
onPressed: () {
repeatUpActivate('900');
},

can this help you? to better understand how to cancel a timer
late Timer _timer;
int i = 0;
#override
void initState() {
// TODO: implement initState
super.initState();
launchTimer();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Test")),
body: Center(
child: ElevatedButton(
child: Text('stop timer'),
onPressed: () async {
stopTimer(stopTimer: true);
},
),
),
);
}
void launchTimer() {
_timer = Timer.periodic(Duration(milliseconds: 200), (timer) {
i++;
print(i);
});
}
void stopTimer({required bool stopTimer}) {
if (stopTimer) {
_timer.cancel();
}
}

Related

Flutter - Handling Life Cycle

Thanks for reading my question.
I wonder why
Unhandled Exception: setState() called after
dispose():_PomodoroState#42b6e(lifecycle state: defunct, not mounted)
is occured when I leave that page without pausing my timer function!
and also I want to know if it's fine to leave it like this.
But I think it's not good idea so I want some advices from you guys
This is my timer code.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:cat_app/provider/counts.dart';
class Pomodoro extends StatefulWidget {
#override
_PomodoroState createState() => _PomodoroState();
}
class _PomodoroState extends State<Pomodoro> {
double coinCount = 0;
Stopwatch watch = Stopwatch();
late Timer timer;
bool startStop = true;
String elapsedTime = '';
updateTime(Timer timer) {
if (watch.isRunning) {
setState(() {
print("startstop Inside=$startStop");
elapsedTime = transformMilliSeconds(watch.elapsedMilliseconds);
});
if (coinCount == 360000) {
context.read<Counts>().add(1);
coinCount = 0;
} else {
coinCount = coinCount + 10;
}
context.read<Times>().timeAdd(100);
}
}
restartTimer() {
updateTime(timer).cancel();
setState(() {
startStop = true;
watch.stop();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.white,
leading: IconButton(
icon: Icon(Icons.arrow_back, color: Colors.black),
onPressed: () => Navigator.of(context).pop(),
),
),
body: Container(
padding: EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(elapsedTime,
style: TextStyle(fontFamily: 'Kitto', fontSize: 25.0)),
SizedBox(height: 20.0),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
IconButton(
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
onPressed: () => startOrStop(),
icon: Image.asset('assets/button.png'),
iconSize: 50,
)
],
)
],
),
),
);
}
startOrStop() {
if (startStop) {
startWatch();
} else {
stopWatch();
}
}
startWatch() {
setState(() {
startStop = false;
watch.start();
timer = Timer.periodic(Duration(milliseconds: 100), updateTime);
});
}
stopWatch() {
setState(() {
startStop = true;
watch.stop();
setTime();
});
}
setTime() {
var timeSoFar = watch.elapsedMilliseconds;
setState(() {
elapsedTime = transformMilliSeconds(timeSoFar);
});
}
transformMilliSeconds(int milliseconds) {
int hundreds = (milliseconds / 10).truncate();
int seconds = (hundreds / 100).truncate();
int minutes = (seconds / 60).truncate();
int hours = (minutes / 60).truncate();
String hoursStr = (hours % 60).toString().padLeft(2, '0');
String minutesStr = (minutes % 60).toString().padLeft(2, '0');
String secondsStr = (seconds % 60).toString().padLeft(2, '0');
return "$hoursStr:$minutesStr:$secondsStr";
}
}
Can you tell me what is wrong?? I want to handle it!
dispose() is used to execute code when the screen is disposed. Equal to onDestroy() of Android.
Example:
#override
void dispose() {
anyController?.dispose();
timer.cancel();
super.dispose();
}
Add this in your Code

Dart - execute a function after x seconds unless cancelled by event

I am currently writing an app using Flutter and Dart. On a button onPressed event I would like to invoke an action that executes after timeLeft seconds unless it is cancelled by correctly entering a pin. Additionally, I would like to use the value timeLeft in a Text widget.
This would require a structure with the following functionality:
executing a function after an x amount of seconds
this function should execute unless some event (e.g. entering a pin correctly) has occurred.
the timeLeft value should be accessible to be used in a Text widget and should update as the timer progresses.
I am wondering how to do this according to flutter's and dart's best practices. For state management I am using the provider pattern so preferably this approach is compatible with the provider pattern.
This is what I have tried so far:
class Home extends ChangeNotifier {
int secondsLeft = 10;
void onPressedEmergencyButton(BuildContext context) {
countDown();
showDialog<void>(
context: context,
builder: (context) {
return ScreenLock(
title: Text(
"Sending message in ${context.read<Home>().secondsLeft} seconds"),
correctString: '1234',
canCancel: false,
didUnlocked: () {
Navigator.pop(context);
},
);
},
);
}
void countDown() {
Future.delayed(const Duration(seconds: 1), () {
secondsLeft =- 1;
notifyListeners();
if (secondsLeft <= 0) {
// Do something
return;
}
});
}
}
You can use CancelableOperation from async package.
Simplifying code-snippet and about _cancelTimer(bool) , this bool used to tell widget about true = time end, and on cancel false like _cancelTimer(false);, rest are described on code-comments.
class TS extends StatefulWidget {
const TS({Key? key}) : super(key: key);
#override
State<TS> createState() => _TSState();
}
class _TSState extends State<TS> {
Timer? _timer;
final Duration _refreseRate = const Duration(seconds: 1);
CancelableOperation? _cancelableOperation;
Duration taskDuration = const Duration(seconds: 5);
bool isSuccess = false;
_initTimer() {
if (_cancelableOperation != null) {
_cancelTimer(false);
}
_cancelableOperation = CancelableOperation.fromFuture(
Future.delayed(Duration.zero),
).then((p0) {
_timer = Timer.periodic(_refreseRate, (timer) {
setState(() {
taskDuration -= _refreseRate;
});
if (taskDuration <= Duration.zero) {
/// task complete on end of duration
_cancelTimer(true);
}
});
}, onCancel: () {
_timer?.cancel();
setState(() {});
});
}
_cancelTimer(bool eofT) {
// cancel and reset everything
_cancelableOperation?.cancel();
_timer?.cancel();
_timer = null;
taskDuration = const Duration(seconds: 5);
isSuccess = eofT;
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (isSuccess)
Container(
height: 100,
width: 100,
color: Colors.green,
),
if (_timer != null)
Text("${taskDuration.inSeconds}")
else
const Text("init Timer"),
],
),
),
floatingActionButton: Row(
mainAxisSize: MainAxisSize.min,
children: [
FloatingActionButton(
child: const Text("init"),
onPressed: () {
_initTimer();
},
),
FloatingActionButton(
child: const Text("Cancel"),
onPressed: () {
_cancelTimer(false);
},
),
],
),
);
}
}
You can use the Timer class to run a function after a set Duration. It doesn't give you the time remaining, but you can calculate it yourself.
Here is a quick implementation I put together:
import 'dart:async';
import 'package:flutter/material.dart';
void main() async {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.light(),
home: const Scaffold(
body: Center(
child: Countdown(),
),
),
);
}
}
class Countdown extends StatefulWidget {
const Countdown({Key? key}) : super(key: key);
#override
_CountdownState createState() => _CountdownState();
}
class _CountdownState extends State<Countdown> {
bool active = false;
Timer? timer;
Timer? refresh;
Stopwatch stopwatch = Stopwatch();
Duration duration = const Duration(seconds: 5);
_CountdownState() {
// this is just so the time remaining text is updated
refresh = Timer.periodic(
const Duration(milliseconds: 100), (_) => setState(() {}));
}
void start() {
setState(() {
active = true;
timer = Timer(duration, () {
stop();
onCountdownComplete();
});
stopwatch
..reset()
..start();
});
}
void stop() {
setState(() {
active = false;
timer?.cancel();
stopwatch.stop();
});
}
void onCountdownComplete() {
showDialog(
context: context,
builder: (context) => const AlertDialog(
title: Text('Countdown was not stopped!'),
),
);
}
int secondsRemaining() {
return duration.inSeconds - stopwatch.elapsed.inSeconds;
}
#override
void dispose() {
timer?.cancel();
refresh?.cancel();
stopwatch.stop();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
if (active) Text(secondsRemaining().toString()),
if (active)
TextButton(onPressed: stop, child: const Text('Stop'))
else
TextButton(onPressed: start, child: const Text('Start')),
],
);
}
}

How to keep a widget expanded based on user's taps?

I want to create a custom widget in which when I click a button,
it expands to a widget something like this [ - 0 + ]
if the user taps '-' or '+', the button should remain expanded and should decrement or increment the value for the respective taps. The current value should be reflected.
if the user does not tap anything after expansion, the widget should collapse to its previous state after some x duration (say 2 seconds) from the previous tap.
Here's is my code
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
MyWidgetState createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
bool isExpanded = false;
int quantity = 0;
int duration = 3;
#override
Widget build(BuildContext context) {
return !isExpanded
? ElevatedButton(
child: Text('Expand (current: $quantity)'),
onPressed: expansionHandler)
: Card(
elevation: 5.0,
child: Container(
color: Colors.blue,
width: 200,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
icon: const Icon(Icons.remove),
onPressed: decrementValue),
Text('$quantity'),
IconButton(
icon: const Icon(Icons.add),
onPressed: incrementValue),
])));
}
void incrementValue() {
setState(() {
duration += 3;
quantity++;
});
}
void decrementValue() {
setState(() {
duration += 3;
quantity--;
quantity = quantity < 0 ? 0 : quantity;
});
}
void expansionHandler() async {
setState(() {
isExpanded = true;
});
await Future.delayed(Duration(seconds: duration+3)/*not sure if this can work or not*/, () {
setState(() {
isExpanded = false;
duration = 2;
});
});
}
}
DartPad Link: https://dartpad.dev/?id=c22ea14c649cc2858f6d09270a62a0d5&null_safety=true
Test this Widget. I comment down the duration changes for fast testing.
I think the problem with expansionHandler with Future that it is activated when you click the 1st time. On second and next click, it generates a new instance instead of replacing the current one.
According to you question, we to check the current state of Timer, where 'Future.delay' can't handle in this case.
Hope this is what you need.
class MyWidget extends StatefulWidget {
#override
MyWidgetState createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
bool isExpanded = false;
int quantity = 0;
int duration = 3;
Timer? timer;
setUpTimer() async {
print("startTimer");
setState(() {
isExpanded = true;
});
timer = await Timer.periodic(Duration(seconds: duration), (timer) {
setState(() {
isExpanded = false;
duration = 2;
});
timer.cancel();
print("done with time $duration");
});
}
_reset() {
if (timer != null && timer!.isActive) timer!.cancel();
setUpTimer();
}
#override
void dispose() {
if (timer != null && timer!.isActive) timer!.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return !isExpanded
? ElevatedButton(
child: Text('Expand (current: $quantity)'),
onPressed: _reset,
)
: Card(
elevation: 5.0,
child: Container(
color: Colors.blue,
width: 200,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
icon: const Icon(Icons.remove),
onPressed: decrementValue),
Text('$quantity'),
IconButton(
icon: const Icon(Icons.add),
onPressed: incrementValue,
),
],
),
),
);
}
void incrementValue() {
setState(() {
// duration += 3;
///for fast testing
quantity++;
});
_reset();
}
void decrementValue() {
setState(() {
// duration += 3;
quantity--;
quantity = quantity < 0 ? 0 : quantity;
});
}
}

stopwatch and timer, problems with flutter

I'm new with dart and flutter and currently I'm studying so please don't judge me for this (maybe) symple question.
I am trying to build a timer but all my code started from a stopwatch so I guess that's the first problem.
In few words, I'm trying to create a timer from 3 minutes that I can stop and start anytime I want (it suppose to be a referee tool) but my starting string is '03:00' and I can see that, so that's fine, I can't find any answer though on how the time can run from 03:00 minutes to 00:00.
Also, as you can see in my code I created a button to reset the time but it always goes back to 00:00 and not to 03:00.
Anyone who can help? I'm definitely missing something.
import 'dart:async';
import 'package:flutter/material.dart';
class NewStopWatch extends StatefulWidget {
#override
_NewStopWatchState createState() => _NewStopWatchState();
}
class _NewStopWatchState extends State<NewStopWatch> {
Stopwatch watch = Stopwatch();
Timer timer;
bool startStop = true;
IconData btnPlayStatus = Icons.play_arrow;
String elapsedTime = '03:00';
updateTime(Timer timer) {
if (watch.isRunning) {
setState(() {
elapsedTime = transformMilliSeconds(watch.elapsedMilliseconds);
});
}
}
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(20.0),
child: Column(
children: <Widget>[
Text(elapsedTime, style: TextStyle(fontSize: 60.0)),
SizedBox(height: 20.0),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 100,
height: 50,
child: FloatingActionButton(
shape: ContinuousRectangleBorder(),
heroTag: "btn1",
backgroundColor: Colors.blueGrey,
onPressed: () => startOrStop(),
child: Icon(btnPlayStatus)),
),
SizedBox(width: 20.0),
Container(
width: 30,
height: 50,
child: FloatingActionButton(
shape: ContinuousRectangleBorder(),
heroTag: "btn2",
backgroundColor: Colors.blueGrey,
onPressed: () => resetWatch(), //resetWatch,
child: Icon(Icons.subdirectory_arrow_left)),
),
],
)
],
),
);
}
resetWatch() {
setState(() {
watch.reset();
setTime();
});
}
startOrStop() {
if(startStop) {
setState(() {
btnPlayStatus = Icons.pause;
});
startWatch();
} else {
setState(() {
btnPlayStatus = Icons.play_arrow;
});
stopWatch();
}
}
startWatch() {
setState(() {
startStop = false;
watch.start();
timer = Timer.periodic(Duration(milliseconds: 100), updateTime);
});
}
stopWatch() {
setState(() {
startStop = true;
watch.stop();
setTime();
});
}
setTime() {
var timeSoFar = watch.elapsedMilliseconds;
setState(() {
elapsedTime = transformMilliSeconds(timeSoFar);
});
}
transformMilliSeconds(int milliseconds) {
int hundreds = (milliseconds / 10).truncate();
int seconds = (hundreds / 100).truncate();
int minutes = (seconds / 60).truncate();
String minutesStr = (minutes % 60).toString().padLeft(2, '0');
String secondsStr = (seconds % 60).toString().padLeft(2, '0');
return "$minutesStr:$secondsStr";
}
}
First of all you need to think if you always want to start from 3 minutes; if so create a static field as follows:
static duration = new Duration(minutes:3);
Edit: I've refactored your code and made it working.
updateTimer(Timer t) {
if (watch.isRunning) {
setState(() {
Duration newDuration = _NewStopWatchState.duration -
new Duration(milliseconds: watch.elapsedMilliseconds);
elapsedTime = durationToMinutesAndSeconds(newDuration);
});
}
}
This is the updateTimer function.
Next, you didn't update your stopWatch() function to take care of the new changes so I changed it for you.
stopWatch() {
setState(() {
startStop = true;
watch.stop();
Duration newDuration = _NewStopWatchState.duration -
new Duration(milliseconds: watch.elapsedMilliseconds);
elapsedTime = durationToMinutesAndSeconds(newDuration);
});
}
I've also updated the resetWatch() function
resetWatch() {
setState(() {
watch.reset();
elapsedTime = durationToMinutesAndSeconds(_NewStopWatchState.duration);
});
}
I've also created an utility function to convert a duration to minutes and seconds.
String durationToMinutesAndSeconds(Duration d) {
return "${d.inMinutes.toString().padLeft(2, '0')}" +
":${d.inSeconds.remainder(60).toString().padLeft(2, '0')}";
}
I've tried it on my machine and the code is working, hope this time is working even on your side.
That's the modified code:
import 'dart:async';
import 'package:flutter/material.dart';
class NewStopWatch extends StatefulWidget {
#override
_NewStopWatchState createState() => _NewStopWatchState();
}
class _NewStopWatchState extends State<NewStopWatch> {
Stopwatch watch = Stopwatch();
Timer timer;
bool startStop = true;
static Duration duration = Duration(minutes:3);
IconData btnPlayStatus = Icons.play_arrow;
String elapsedTime = '';
updateTimer() {
if (watch.isRunning) {
setState(() {
elapsedTime = transformMilliSeconds(_NewStopWatchState.duration.inMilliseconds - watch.elapsedMilliseconds);
});
}
}
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(20.0),
child: Column(
children: <Widget>[
Text(elapsedTime),
SizedBox(height: 20.0),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 100,
height: 50,
child: FloatingActionButton(
shape: ContinuousRectangleBorder(),
heroTag: "btn1",
backgroundColor: Colors.blueGrey,
onPressed: () => startOrStop(),
child: Icon(btnPlayStatus)),
),
SizedBox(width: 20.0),
Container(
width: 30,
height: 50,
child: FloatingActionButton(
shape: ContinuousRectangleBorder(),
heroTag: "btn2",
backgroundColor: Colors.blueGrey,
onPressed: () => resetWatch(), //resetWatch,
child: Icon(Icons.subdirectory_arrow_left)),
),
],
),
],
),
);
}
resetWatch() {
setState(() {
watch.reset();
setTime();
elapsedTime="${_NewStopWatchState.duration
.inMinutes}:${_NewStopWatchState.duration.inSeconds.remainder(60)}";
});
}
startOrStop() {
if(startStop) {
setState(() {
btnPlayStatus = Icons.pause;
});
startWatch();
} else {
setState(() {
btnPlayStatus = Icons.play_arrow;
});
stopWatch();
}
}
startWatch() {
setState(() {
startStop = false;
watch.start();
timer = Timer.periodic(Duration(milliseconds: 100), updateTimer());
});
}
stopWatch() {
setState(() {
startStop = true;
watch.stop();
setTime();
});
}
setTime() {
var timeSoFar = watch.elapsedMilliseconds;
setState(() {
elapsedTime = transformMilliSeconds(timeSoFar);
});
}
transformMilliSeconds(int milliseconds) {
int hundreds = (milliseconds / 10).truncate();
int seconds = (hundreds / 100).truncate();
int minutes = (seconds / 60).truncate();
String minutesStr = (minutes % 60).toString().padLeft(2, '0');
String secondsStr = (seconds % 60).toString().padLeft(2, '0');
return "$minutesStr:$secondsStr";
}
}
If you want a stop watch also with timer you can use my easy github project.
I hope this will will help some one. Thank you.
Here you can get my github repo

How to Check if Timer is Active Before Creating a New One

I came across this code for a timer on another thread. When you press the RaisedButton multiple times concurrently it adds a -1 second for every click thus increasing the rate of decrease.
Any ideas for the easiest way to check if the timer is already active and if it is to not let the RaisedButton create a new one. Thanks!
import 'dart:async';
[...]
Timer _timer;
int _start = 10;
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
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")
],
),
);
}
Add a check to see if _timer is already active using the isActive property of Timers. If it's already active, it won't create a new one.
Example:
void startTimer() {
const oneSec = const Duration(seconds: 1);
if(!_timer?.isActive) {
_timer = new Timer.periodic(
oneSec,
(Timer timer) => setState(
() {
if (_start < 1) {
timer.cancel();
} else {
_start = _start - 1;
}
},
),
);
}
}
The receiver can't be null, so the null-aware operator '?.' is unnecessary.
Change this:
if(!_timer?.isActive)
to
if(!_timer.isActive)