Skip to content

Instantly share code, notes, and snippets.

@lrhn
Created October 5, 2021 09:13
Show Gist options
  • Save lrhn/9568522557db0b62d987185e0ff01ae7 to your computer and use it in GitHub Desktop.
Save lrhn/9568522557db0b62d987185e0ff01ae7 to your computer and use it in GitHub Desktop.
Dart expiring cache
// Copyright 2021 Google LLC.
// SPDX-License-Identifier: BSD-3-Clause
/// Cache for a value which invalidates itself after a predetermined duration.
///
/// The cache will store a [value], but after a specified keep-alive duration
/// has passed, the cache will invalidate itself and no longer provide access
/// to the cached value.
///
/// Reading [value] will return either the cached value, or `null` if the
/// cache is no longer valid.
/// Writing to [value] will set a new cached value and restart the keep-alive
/// duration. You can write to the cache before it invalidates, and you
/// can invalidate it early by calling [clear].
/// If no new writes happen to [value] before the keep-alive duration has
/// expired, the [value] will return `null` on the further reads.
///
/// Example:
/// ```dart
/// final _nowCache = ExpiringCache<DateTime>(const Duration(minutes: 5));
/// DateTime get _now => _nowCache.value ??= DateTime.now());
/// // ...
/// doSomething(_now);
/// ```
/// This cache will automatically clear, and its [value] become `null`,
/// five minutes after a value was last set. The `_now` helper getter here
/// will reuse the cache value when it's there, and write a new value
/// when the cached value is no longer available.
///
/// **Notice:** The cache *lazily* invalidates itself, so the cached value won't
/// be garbage collectable until you either call [clear] or read the [value]
/// and sees it being `null`.
class ExpiringCache<T> {
/// Duration to keep cached values valid.
final Duration _keepAlive;
/// Clock which starts ticking when a value is stored.
///
/// The cache is valid if this timer is running and below [_keepAlive].
/// If it runs past [_keepAlive] then the next check of [value]
/// or [hasValue] will see the cache as expired.
/// If [cancel]ed before that, we stop the stopwatch instead, since we have
/// now way to advance it past [_keepAlive]. The stopwatch is also stopped
/// until the first value is cached.
final Stopwatch _timer = Stopwatch();
T? _value;
/// Creates a cache which keeps its value alive for a duration of [keepAlive].
ExpiringCache(Duration keepAlive) : _keepAlive = keepAlive;
/// Checks whether the cache currently has a value.
///
/// If the cache has a value then the timer is active and its elapsed time is
/// below [_keepAlive].
bool get _hasValue => _timer.isRunning && _timer.elapsedMicroseconds <= _keepAlive.inMicroseconds;
/// The value currently stored in the cache, or `null` if the cache has no value.
///
/// Retains the last value stored until the keep-alive duration has passed,
/// or until someone explicitly calls [clear].
/// When the cache has expired or been cleared, the value is [null] instead.
///
/// If the value type of the cache is nullable, you can use [hasValue] to see
/// whether a value of [null] is a valid cached value or it represents
/// the absence of a value.
T? get value => _hasValue ? _value : (_value = null);
void set value(T value) {
_value = value;
_timer..reset()..start();
}
/// Clears and invalidates the cache.
///
/// Clears the cached value, if any.
/// If the cache is currently valid, it is invalidated. That is,
/// after clearing, [hasValue] is `false` and [value] is `null`.
void clear() {
_value = null;
_timer.stop(); // Stop timer to mark value as invalidated.
}
/// Whether the cache currently has a value.
///
/// If the value type is nullable, it might be valuable to distinguish a
/// cached value of `null` from a cleared cache providing `null` instead
/// of a value.
/// For non-nullable value types, it's usually more convenient to just
/// check whether [value] is `null`, since that can be combined with
/// using the value when it's not `null`.
bool get hasValue => _hasValue;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment