Last active
February 13, 2024 13:03
-
-
Save lukepighetti/82bdea189049fdebc49a563ba2c713c0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import '/features/architecture/logging.dart'; | |
class CachedQuery<T> { | |
final Duration invalidation; | |
final Future<T> Function(String key) fn; | |
CachedQuery( | |
this.fn, | |
this.invalidation, | |
); | |
final _log = Logger('CachedQuery<$T>'); | |
final _cache = <String, (T, DateTime expiresAt)>{}; | |
void invalidate(String key) { | |
_cache.remove(key); | |
} | |
Future<T> invalidateAndFetch(String key) { | |
invalidate(key); | |
return fetch(key); | |
} | |
void invalidateAll() { | |
_cache.clear(); | |
} | |
Future<T> call([String key = '']) { | |
return fetch(key); | |
} | |
Future<T> fetch([String key = '']) async { | |
final (cached, expiresAt) = _cache[key] ?? (null, DateTime(-1)); | |
final now = DateTime.now(); | |
if (cached != null && now.isBefore(expiresAt)) { | |
return cached; | |
} else { | |
try { | |
final value = await fn(key); | |
final expiry = now.add(invalidation); | |
_cache[key] = (value, expiry); | |
return value; | |
} catch (e, trace) { | |
_log.e('', e, trace); | |
rethrow; | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extension ChangeNotifierExtensions on ChangeNotifier { | |
void setState(VoidCallback fn) { | |
fn(); | |
// ignore: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member | |
notifyListeners(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// remote state | |
final userSettings = FirebaseAuth.instance | |
.userChanges() | |
.map((it) => it?.uid) | |
.mapStreamOrNull(FirebaseFirestore.instance.watchUserSettings) | |
.toStateStream(null); | |
main() async* { | |
print('latest value ${userSettings.value}'); | |
await for (final value in userSettings) { | |
print('new value $value'); | |
} | |
} | |
// ephemeral state in StatefulWidgets | |
// big chunks of ephemeral state | |
final onboardingState = OnboardingState(); | |
class OnboardingState extends ChangeNotifier { | |
var onboarded = false; | |
void completeOnboarding() => setState(() => onboarded = true); | |
} | |
// cached futures | |
final userQuery = CachedQuery( | |
(String uid) => getUser(id), | |
Duration(seconds: 30), | |
); | |
main() async { | |
print(await userQuery('me')); // hits network | |
print(await userQuery('me')); // hits cache | |
print(await userQuery('me')); // hits cache | |
userQuery.invalidate('me'); | |
print(await userQuery('me')); // hits network | |
await Future.delayed(Duration(seconds: 31)); | |
print(await userQuery('me')); // hits network | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extension StreamExtensions<T> on Stream<T> { | |
Stream<E> mapFuture<E>(FutureOr<E> Function(T) fn) => asyncMap(fn); | |
Stream<S> mapStream<S>(Stream<S> Function(T) fn) => switchMap(fn); | |
ValueStream<T> toStateStream(T seed) => | |
distinct().shareValueSeeded(seed).keepAlive(); | |
} | |
extension NullableStreamExtensions<T> on Stream<T?> { | |
Stream<E?> mapOrNull<E>(E Function(T) fn) { | |
return map((val) => val?.let(fn)); | |
} | |
Stream<E?> mapFutureOrNull<E>(Future<E> Function(T) fn) { | |
return mapFuture((val) => val?.let(fn) ?? Future.value(null)); | |
} | |
Stream<S?> mapStreamOrNull<S>(Stream<S> Function(T) fn) { | |
return mapStream((val) => val?.let(fn) ?? Stream.value(null)); | |
} | |
} | |
extension ValueStreamX<T> on ValueStream<T> { | |
// .shareValue / .shareValueSeeded close when there are no listeners | |
// which happens on hot reload or app pause. So this is a shim to | |
// keep these ValueStreams alive | |
ValueStream<T> keepAlive() { | |
listen((_) {}); | |
return this; | |
} | |
} | |
extension NullableValueStreamExtensions<T> on ValueStream<T?> { | |
/// Returns the first available non-null value. Tries [value] falling back | |
/// to waiting for matching stream events | |
Future<T> get firstNotNull async { | |
final x = value; | |
if (x != null) { | |
return x; | |
} else { | |
return await firstWhere((it) => it != null) as T; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment