Skip to content

Commit

Permalink
Implement periodic timer (#64)
Browse files Browse the repository at this point in the history
  • Loading branch information
mateusfccp authored Oct 29, 2023
1 parent 0c44383 commit d27eafd
Show file tree
Hide file tree
Showing 5 changed files with 556 additions and 174 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 3.1.0

- Provide `.periodic` constructor (#45)
- A periodic timer should behave similar to [Timer.periodic](https://api.dart.dev/stable/dart-async/Timer/Timer.periodic.html)

## 3.0.0

- **BREAKING:** `PausableTimer` is now `final`
Expand Down
18 changes: 7 additions & 11 deletions example/countdown.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// Example on how to implement countdown making a PausableTimer periodic.
import 'dart:async';

import 'package:pausable_timer/pausable_timer.dart';

void main() async {
Expand All @@ -7,21 +9,15 @@ void main() async {
var countDown = 5;

print('Create a periodic timer that fires every 1 second and starts it');
timer = PausableTimer(
timer = PausableTimer.periodic(
Duration(seconds: 1),
() {
countDown--;
// If we reached 0, we don't reset and restart the time, so it won't fire
// again, but it can be reused afterwards if needed. If we cancel the
// timer, then it can be reused after the countdown is over.
if (countDown > 0) {
// we know the callback won't be called before the constructor ends, so
// it is safe to use !
timer
..reset()
..start();

if (countDown == 0) {
timer.pause();
}
// This is really what your callback do.

print('\t$countDown');
},
)..start();
Expand Down
84 changes: 66 additions & 18 deletions lib/pausable_timer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ final class PausableTimer implements Timer {
/// When the timer expires, this stopwatch is set to null.
Stopwatch? _stopwatch = clock.stopwatch();

final bool _periodic;

/// The currently active [Timer].
///
/// This is null whenever this timer is not currently active.
Expand All @@ -61,45 +63,91 @@ final class PausableTimer implements Timer {
/// should make sure the timer wasn't cancelled before calling this function.
void _startTimer() {
assert(_stopwatch != null);
_timer = _zone.createTimer(
_originalDuration - _stopwatch!.elapsed,
() {
_tick++;
_timer = null;
_stopwatch = null;
_zone.run(_callback!);
},
);

if (_periodic && _stopwatch!.elapsed == Duration.zero) {
_timer = _zone.createPeriodicTimer(
duration,
(Timer timer) {
_tick++;
_zone.run(_callback!);
_stopwatch = clock.stopwatch();
_stopwatch!.start();
},
);
} else {
_timer = _zone.createTimer(
duration - _stopwatch!.elapsed,
() {
_tick++;
if (_periodic) {
reset();
} else {
_timer = null;
_stopwatch = null;
}
_zone.run(_callback!);
},
);
}

_stopwatch!.start();
}

/// Creates a new timer.
/// Creates a new pausable timer.
///
/// The [callback] is invoked after the given [duration], but can be [pause]d
/// in between or [reset]. The [elapsed] time is only accounted for while the
/// timer [isActive].
/// timer is active.
///
/// The timer is paused when created, and must be [start]ed manually.
///
/// The [duration] must be equals or bigger than [Duration.zero].
/// If it is [Duration.zero], the [callback] will still not be called until
/// the timer is [start]ed.
PausableTimer(this.duration, void Function() callback)
: assert(duration >= Duration.zero),
_zone = Zone.current,
_periodic = false {
_callback = _zone.bindCallback(callback);
}

/// Creates a new repeating pausable timer.
///
/// The [callback] is invoked repeatedly with [duration] intervals until
/// canceled with the [cancel] function, but can be [pause]d
/// in between or [reset]. The [elapsed] time is only accounted for while the
/// timer is active.
///
/// The timer is paused when created, and must be [start]ed manually.
///
/// The exact timing depends on the underlying timer implementation.
/// No more than `n` callbacks will be made in `duration * n` time,
/// but the time between two consecutive callbacks
/// can be shorter and longer than `duration`.
///
/// The timer [isPaused] when created, and must be [start]ed manually.
/// In particular, an implementation may schedule the next callback, e.g.,
/// a `duration` after either when the previous callback ended,
/// when the previous callback started, or when the previous callback was
/// scheduled for - even if the actual callback was delayed.
///
/// The [duration] must be equals or bigger than [Duration.zero].
/// If it is [Duration.zero], the [callback] will still not be called until
/// the timer is [start]ed.
PausableTimer(Duration duration, void Function() callback)
PausableTimer.periodic(this.duration, void Function() callback)
: assert(duration >= Duration.zero),
_originalDuration = duration,
_zone = Zone.current {
_zone = Zone.current,
_periodic = true {
_callback = _zone.bindCallback(callback);
}

/// The original duration this [Timer] was created with.
Duration get duration => _originalDuration;
final Duration _originalDuration;
final Duration duration;

/// The time this [Timer] have been active.
///
/// If the timer is paused, the elapsed time is also not computed anymore, so
/// [elapsed] is always less than or equals to the [duration].
Duration get elapsed => _stopwatch?.elapsed ?? _originalDuration;
Duration get elapsed => _stopwatch?.elapsed ?? duration;

/// True if this [Timer] is armed but not currently active.
///
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ funding:
- https://github.com/sponsors/mateusfccp
- https://github.com/llucax/llucax/blob/main/sponsoring-platforms.md

version: 3.0.0
version: 3.1.0

environment:
sdk: ">=3.0.0 <4.0.0"
Expand Down
Loading

0 comments on commit d27eafd

Please sign in to comment.