• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5part of engine;
6
7/// A function that returns current system time.
8typedef TimestampFunction = DateTime Function();
9
10/// Notifies the [callback] at the given [datetime].
11///
12/// Allows changing [datetime] in either direction before the alarm goes off.
13///
14/// The implementation uses [Timer]s and therefore does not guarantee that it
15/// will go off precisely at the specified time. For more details see:
16///
17/// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Notes
18class AlarmClock {
19  AlarmClock(TimestampFunction timestampFunction)
20      : _timestampFunction = timestampFunction;
21
22  /// The function used to get current time.
23  final TimestampFunction _timestampFunction;
24
25  /// The underlying timer used to schedule the callback.
26  Timer _timer;
27
28  /// Current target time the [callback] is scheduled for.
29  DateTime _datetime;
30
31  /// The callback called when the alarm goes off.
32  ui.VoidCallback callback;
33
34  /// The time when the alarm clock will go off.
35  ///
36  /// If the time is in the past or is `null` the alarm clock will not go off.
37  ///
38  /// If the value is updated before an already scheduled timer goes off, the
39  /// previous time will not call the [callback]. Think of the updating this
40  /// value as "changing your mind" about when you want the next timer to fire.
41  DateTime get datetime => _datetime;
42  set datetime(DateTime value) {
43    if (value == _datetime) {
44      return;
45    }
46
47    if (value == null) {
48      _cancelTimer();
49      _datetime = null;
50      return;
51    }
52
53    final DateTime now = _timestampFunction();
54
55    // We use the "not before" logic instead of "is after" because zero-duration
56    // timers are valid.
57    final bool isInTheFuture = !value.isBefore(now);
58
59    if (!isInTheFuture) {
60      _cancelTimer();
61      _datetime = value;
62      return;
63    }
64
65    // At this point we have a non-null value that's in the future, and it is
66    // different from the current _datetime. We need to decide whether we need
67    // to create a new timer, or keep the existing one.
68    if (_timer == null) {
69      // We didn't have an existing timer, so create a new one.
70      _timer = Timer(value.difference(now), _timerDidFire);
71    } else {
72      assert(_datetime != null,
73          'We can only have a timer if there is a non-null datetime');
74      if (_datetime.isAfter(value)) {
75        // This is the case when the value moves the target time to an earlier
76        // point. Because there is no way to reconfigure an existing timer, we
77        // must cancel the old timer and schedule a new one.
78        _cancelTimer();
79        _timer = Timer(value.difference(now), _timerDidFire);
80      }
81      // We don't need to do anything in the "else" branch. If the new value
82      // is in the future relative to the current datetime, the _timerDidFire
83      // will reschedule.
84    }
85
86    _datetime = value;
87  }
88
89  void _cancelTimer() {
90    if (_timer != null) {
91      _timer.cancel();
92      _timer = null;
93    }
94  }
95
96  void _timerDidFire() {
97    assert(_datetime != null,
98        'If _datetime is null, the timer would have been cancelled');
99    final DateTime now = _timestampFunction();
100    // We use the "not before" logic instead of "is after" because we may have
101    // zero difference between now and _datetime.
102    if (!now.isBefore(_datetime)) {
103      _timer = null;
104      callback();
105    } else {
106      // The timer fired before the target date. We need to reschedule.
107      _timer = Timer(_datetime.difference(now), _timerDidFire);
108    }
109  }
110}
111