Show timer progress on a CircularProgressIndicator in flutter
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 Stream
s 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...).
Gil Sand
Now that you've spent that much time to click on my profile, might as well check the crazy things I write on my blog, and the blog I'm writing for RiseUp I’m really just a guy who started programming in 2014. I’m from Belgium and fluently speak French, English, Objective-C, and most importantly C#. I’ve been helped a lot, by many people, this includes the current company I work with, the company that I published my first app with, and the different trainers that thaught me programming in the first place. Now I try to give back as much as I can on StackOverflow. I like it. Otherwise I’m just a regular guy. I play games with my friends while complaining about my loved half, and complain about my friends to her to keep things balanced karma-wise. Generic stuff really. And of course : #SOreadytohelp
Updated on December 12, 2022Comments
-
Gil Sand over 1 year
I'm using a
RestartableTimer
(subclass ofTimer
) 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?
-
Gil Sand almost 5 yearsIts a really good base to what I need ; I'm comfortable enough to understand it. It'll check this out tomorrow during daytime hours. Right now I found that using a Streambuilder + a Stopwatch + Timer, in a separate class. It works but I don't find it super clean yet.