/// Minimal stand-ins for Riverpod so the fixture needs no dependency. pipe /// recognises providers by the `FutureProvider(...)` / `Provider(...)` /// constructor and consumption by `ref.watch(...)`. library; class AsyncData { const AsyncData(this.value); final T value; } class AsyncValue { const AsyncValue(); T? get value => null; AsyncData? get asData => null; bool get hasError => false; R when({ required R Function(T value) data, required R Function() loading, required R Function(Object error, StackTrace stack) error, }) => throw UnimplementedError(); } class AsyncError extends AsyncValue { const AsyncError(); } class AsyncLoading extends AsyncValue { const AsyncLoading(); } class FutureProvider { const FutureProvider(this.create); final Future Function(Ref ref) create; } class Ref { AsyncValue watch(FutureProvider provider) => throw UnimplementedError(); void listen( FutureProvider provider, void Function(AsyncValue? previous, AsyncValue next) listener, ) => throw UnimplementedError(); } class SubscriptionTier {} class UnhandledDoc {} /// Consumed without ever handling loading or error. final unhandledProvider = FutureProvider((ref) async => UnhandledDoc()); /// Consumed with `when(data:, loading:, error:)`. final handledProvider = FutureProvider((ref) async => 1); /// Consumed only by `ref.listen`, to mirror the value into a side effect. final listenOnlyProvider = FutureProvider((ref) async => false); class SubscriptionView { SubscriptionView(this.ref); final Ref ref; /// BAD: silently defaults, so a failure renders the wrong state. Object? renderTier() => ref.watch(unhandledProvider).asData?.value; /// GOOD: every AsyncValue case is handled, so no gap is reported. int renderCount() => ref.watch(handledProvider).when( data: (value) => value, loading: () => -1, error: (error, stack) => -2, ); /// GOOD: pattern-matches AsyncError and AsyncLoading in a switch expression, /// the form the remediation itself recommends. int renderPatterned() => switch (ref.watch(patternedProvider)) { AsyncError() => -2, AsyncLoading() => -1, _ => 0, }; /// GOOD: the same in a switch statement. int renderSwitchStatement() { switch (ref.watch(switchedProvider)) { case AsyncError(): return -2; case AsyncLoading(): return -1; default: return 0; } } /// GOOD: the watched value is stored in a variable and then handled. int renderStored() { final count = ref.watch(storedProvider); return count.when( data: (value) => value, loading: () => -1, error: (error, stack) => -2, ); } /// Direct value reads of the three GOOD providers above. Without a site that /// reads the value, a provider is never reported whatever its handling, so /// those controls would prove nothing. int? peekPatterned() => ref.watch(patternedProvider).value; int? peekSwitched() => ref.watch(switchedProvider).value; int? peekStored() => ref.watch(storedProvider).value; /// BAD: stored in a variable and then read without handling, so following /// the variable must not silence the rule. int? renderStoredUnhandled() { final count = ref.watch(storedUnhandledProvider); return count.value; } /// Not a render: a listener that mirrors the value into a side effect (for /// example an analytics user property) draws nothing, so ignoring loading /// and error here leaves no blank or stale screen. void mirrorFlag(void Function(bool value) sink) { ref.listen(listenOnlyProvider, (previous, next) { final value = next.asData?.value; if (value != null) sink(value); }); } } final patternedProvider = FutureProvider((ref) async => 1); final switchedProvider = FutureProvider((ref) async => 1); final storedProvider = FutureProvider((ref) async => 1); final storedUnhandledProvider = FutureProvider((ref) async => 1);