jld3103
2 years ago
7 changed files with 171 additions and 4 deletions
@ -0,0 +1,85 @@
|
||||
part of '../../neon.dart'; |
||||
|
||||
abstract class TimerBlocEvents { |
||||
/// Register a [callback] that will be called periodically. |
||||
/// The time between the executions is defined by the [duration]. |
||||
NeonTimer registerTimer(final Duration duration, final VoidCallback callback); |
||||
|
||||
/// Unregister a timer that has been previously registered with the bloc. |
||||
/// You can also use [NeonTimer.cancel]. |
||||
void unregisterTimer(final NeonTimer timer); |
||||
} |
||||
|
||||
abstract class TimerBlocStates {} |
||||
|
||||
/// Execute callbacks at defined periodic intervals. |
||||
/// Components can register their callbacks and everything with the same periodicity will be executed at the same time. |
||||
/// |
||||
/// The [TimerBloc] is a singleton. |
||||
/// Sub-second timers are not supported. |
||||
class TimerBloc extends Bloc implements TimerBlocEvents, TimerBlocStates { |
||||
factory TimerBloc() => instance ??= TimerBloc._(); |
||||
|
||||
@visibleForTesting |
||||
factory TimerBloc.mocked(final TimerBloc mock) => instance ??= mock; |
||||
|
||||
TimerBloc._(); |
||||
|
||||
@visibleForTesting |
||||
static TimerBloc? instance; |
||||
|
||||
final Map<int, Timer> _timers = {}; |
||||
final Map<int, Set<VoidCallback>> _callbacks = {}; |
||||
|
||||
@visibleForTesting |
||||
Map<int, Timer> get timers => _timers; |
||||
|
||||
@visibleForTesting |
||||
Map<int, Set<VoidCallback>> get callbacks => _callbacks; |
||||
|
||||
@override |
||||
void dispose() { |
||||
for (final timer in _timers.values) { |
||||
timer.cancel(); |
||||
} |
||||
_timers.clear(); |
||||
_callbacks.clear(); |
||||
TimerBloc.instance = null; |
||||
} |
||||
|
||||
@override |
||||
NeonTimer registerTimer(final Duration duration, final VoidCallback callback) { |
||||
if (_timers[duration.inSeconds] == null) { |
||||
_timers[duration.inSeconds] = Timer.periodic(duration, (final _) { |
||||
for (final callback in _callbacks[duration.inSeconds]!) { |
||||
callback(); |
||||
} |
||||
}); |
||||
_callbacks[duration.inSeconds] = {callback}; |
||||
} else { |
||||
_callbacks[duration.inSeconds]!.add(callback); |
||||
} |
||||
return NeonTimer(duration, callback); |
||||
} |
||||
|
||||
@override |
||||
void unregisterTimer(final NeonTimer timer) { |
||||
if (_timers[timer.duration.inSeconds] != null) { |
||||
_callbacks[timer.duration.inSeconds]!.remove(timer.callback); |
||||
} |
||||
} |
||||
} |
||||
|
||||
class NeonTimer { |
||||
NeonTimer( |
||||
this.duration, |
||||
this.callback, |
||||
); |
||||
|
||||
final Duration duration; |
||||
final VoidCallback callback; |
||||
|
||||
void cancel() { |
||||
TimerBloc().unregisterTimer(this); |
||||
} |
||||
} |
@ -0,0 +1,43 @@
|
||||
import 'package:neon/neon.dart'; |
||||
import 'package:test/test.dart'; |
||||
|
||||
void main() { |
||||
group('TimerBloc', () { |
||||
tearDown(() { |
||||
TimerBloc().dispose(); |
||||
}); |
||||
|
||||
test('Register timer', () async { |
||||
const duration = Duration(milliseconds: 100); |
||||
|
||||
final stopwatch = Stopwatch()..start(); |
||||
final callback = stopwatch.stop; |
||||
TimerBloc().registerTimer(duration, callback); |
||||
await Future.delayed(duration); |
||||
|
||||
expect(stopwatch.elapsedMilliseconds, greaterThan(duration.inMilliseconds)); |
||||
expect(stopwatch.elapsedMilliseconds, lessThan(duration.inMilliseconds * 1.1)); |
||||
expect(TimerBloc().callbacks[duration.inSeconds], contains(callback)); |
||||
expect(TimerBloc().timers[duration.inSeconds], isNot(isNull)); |
||||
}); |
||||
|
||||
test('Unregister timer', () async { |
||||
const duration = Duration(milliseconds: 100); |
||||
final callback = neverCalled; |
||||
|
||||
TimerBloc().registerTimer(duration, callback).cancel(); |
||||
await Future.delayed(duration); |
||||
|
||||
expect(TimerBloc().callbacks[duration.inSeconds], isNot(contains(callback))); |
||||
}); |
||||
|
||||
test('dispose', () { |
||||
TimerBloc().registerTimer(const Duration(minutes: 1), () {}); |
||||
expect(TimerBloc().timers, hasLength(1)); |
||||
expect(TimerBloc().callbacks, hasLength(1)); |
||||
TimerBloc().dispose(); |
||||
expect(TimerBloc().timers, isEmpty); |
||||
expect(TimerBloc().callbacks, isEmpty); |
||||
}); |
||||
}); |
||||
} |
Loading…
Reference in new issue