Dart - execute a function after x seconds unless cancelled by event
Solution 1
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);
},
),
],
),
);
}
}
Solution 2
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')),
],
);
}
}
killerbird
Updated on January 03, 2023Comments
-
killerbird over 1 year
I am currently writing an app using Flutter and Dart. On a button
onPressed
event I would like to invoke an action that executes aftertimeLeft
seconds unless it is cancelled by correctly entering a pin. Additionally, I would like to use the valuetimeLeft
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; } }); } }
-
Yeasin Sheikh over 2 yearsCan you include your code-snippet that you've tried so far
-
killerbird over 2 years@YeasinSheikh check!
- executing a function after an
-
Yeasin Sheikh over 2 yearsDoes
refresh
timer can be nested having multiple click while we can't cancel the future? -
mmcdon20 over 2 yearsI'm not sure I understood what you are asking. I only ever create one instance of the
refresh
timer in the constructor, (and it gets cancelled on dispose). There is a different instance of a timer that gets created every time you click on the "Start" button. And when you click on the "Start" button it is immediately replaced with the "Stop" button which prevents you from clicking on "Start" multiple times in succession. -
Yeasin Sheikh over 2 yearsI think you can test putting something on body of
Timer.periodic
, I've tried this way but failed becauseFuture
cant be canceled as much as I am aware of, you can check this -
mmcdon20 over 2 yearsI don't think I'm following you. When you press the "Stop" button I am cancelling a
Timer
object not aFuture
object. You can cancel aTimer
. I'm not using anyFuture
objects in my solution. -
Yeasin Sheikh over 2 yearsYes you are right, we are just following different way