Files
social-app/apps/lib/data/cache/cached_repository.dart
T

108 lines
2.6 KiB
Dart

import 'dart:async';
import 'cache_entry.dart';
import 'cache_policy.dart';
import 'hybrid_cache_store.dart';
abstract class CachedRepository<T> {
final HybridCacheStore store;
final CachePolicy policy;
final DateTime Function() now;
final Map<String, Future<void>> _refreshInFlight = <String, Future<void>>{};
CachedRepository({
required this.store,
required this.policy,
DateTime Function()? now,
}) : now = now ?? DateTime.now;
Future<T> getOrLoad({
required String key,
required Future<T> Function() loadFromRemote,
bool Function(T loaded)? shouldWriteLoaded,
bool forceRefresh = false,
}) async {
if (forceRefresh) {
return _refreshAndWrite(
key,
loadFromRemote,
shouldWriteLoaded: shouldWriteLoaded,
);
}
final cached = await readCacheEntry(key);
if (cached == null) {
return _refreshAndWrite(
key,
loadFromRemote,
shouldWriteLoaded: shouldWriteLoaded,
);
}
final decision = policy.evaluate(now: now(), fetchedAt: cached.fetchedAt);
if (decision.shouldRefreshInBackground) {
refreshInBackground(
key: key,
loadFromRemote: loadFromRemote,
shouldWriteLoaded: shouldWriteLoaded,
);
}
if (decision.mustBlockForNetwork || !decision.canUseCached) {
return _refreshAndWrite(
key,
loadFromRemote,
shouldWriteLoaded: shouldWriteLoaded,
);
}
return cached.value;
}
Future<CacheEntry<T>?> readCacheEntry(String key) {
return store.read<CacheEntry<T>>(key);
}
Future<void> writeCacheEntry(String key, T value) {
return store.write<CacheEntry<T>>(
key,
CacheEntry<T>(value: value, fetchedAt: now()),
);
}
Future<void> removeCacheKey(String key) {
return store.remove(key);
}
void refreshInBackground({
required String key,
required Future<T> Function() loadFromRemote,
bool Function(T loaded)? shouldWriteLoaded,
}) {
if (_refreshInFlight.containsKey(key)) {
return;
}
final task = _refreshAndWrite(
key,
loadFromRemote,
shouldWriteLoaded: shouldWriteLoaded,
).then((_) {});
final tracked = task.whenComplete(() {
_refreshInFlight.remove(key);
});
_refreshInFlight[key] = tracked;
unawaited(tracked);
}
Future<T> _refreshAndWrite(
String key,
Future<T> Function() loadFromRemote, {
bool Function(T loaded)? shouldWriteLoaded,
}) async {
final remote = await loadFromRemote();
if (shouldWriteLoaded != null && !shouldWriteLoaded(remote)) {
return remote;
}
await writeCacheEntry(key, remote);
return remote;
}
}