Last active
November 2, 2025 20:21
-
-
Save gruvw/61c5e5330eb585220fd90eb73ee38d34 to your computer and use it in GitHub Desktop.
This file contains hidden or 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 'dart:math'; | |
| import 'package:riverpod/riverpod.dart'; | |
| final random = Random(); | |
| Stream<bool> createStream() async* { | |
| final duration = Duration(milliseconds: 1000 + random.nextInt(1000)); | |
| await Future.delayed(duration); | |
| yield true; | |
| await Future.delayed(duration); | |
| yield true; | |
| await Future.delayed(duration); | |
| yield false; | |
| } | |
| final streamProvider1 = StreamProvider((ref) => createStream()); | |
| final streamProvider2 = StreamProvider((ref) => createStream()); | |
| final streamProvider3 = StreamProvider((ref) => createStream()); | |
| // will create synthetic loading states | |
| final faultyNonProvider = FutureProvider((ref) async { | |
| final value = await ref.watch(streamProvider1.future); | |
| return !value; | |
| }); | |
| // won't create synthetic loading states | |
| final correctNonProvider = Provider<AsyncValue<bool>>((ref) { | |
| final valueAsync = ref.watch(streamProvider1); | |
| return valueAsync.whenData((value) => !value); | |
| }); | |
| // will create synthetic loading states | |
| final faultyCombineProvider = FutureProvider((ref) async { | |
| final value1 = await ref.watch(streamProvider1.future); | |
| final value2 = await ref.watch(streamProvider2.future); | |
| final value3 = await ref.watch(streamProvider3.future); | |
| return (value1 && value2) || value3; | |
| }); | |
| // combine async values without synthetic loading states, requires nesting | |
| final verboseCombineProvider = Provider<AsyncValue<bool>>((ref) { | |
| final value1Async = ref.watch(streamProvider1); | |
| return value1Async.when( | |
| skipLoadingOnRefresh: false, | |
| data: (value1) { | |
| final value2Async = ref.watch(streamProvider2); | |
| return value2Async.when( | |
| skipLoadingOnRefresh: false, | |
| data: (value2) { | |
| final value3Async = ref.watch(streamProvider2); | |
| return value3Async.whenData((value3) { | |
| return (value1 && value2) || value3; | |
| }); | |
| }, | |
| error: (error, stackTrace) => AsyncError(error, stackTrace), | |
| loading: () => AsyncLoading(), | |
| ); | |
| }, | |
| error: (error, stackTrace) => AsyncError(error, stackTrace), | |
| loading: () => AsyncLoading(), | |
| ); | |
| }); | |
| // extension to reduce verbosity | |
| extension MapData<T> on AsyncValue<T> { | |
| AsyncValue<E> mapData<E>(AsyncValue<E> Function(T value) data) { | |
| return when( | |
| skipLoadingOnRefresh: false, | |
| data: data, | |
| error: (error, stackTrace) => AsyncError(error, stackTrace), | |
| loading: () => AsyncLoading(), | |
| ); | |
| } | |
| } | |
| // combine async values without synthetic loading states, still requires nesting | |
| final nestedCombineProvider = Provider<AsyncValue<bool>>((ref) { | |
| final value1Async = ref.watch(streamProvider1); | |
| return value1Async.mapData((value1) { | |
| final value2Async = ref.watch(streamProvider2); | |
| return value2Async.mapData((value2) { | |
| final value3Async = ref.watch(streamProvider2); | |
| return value3Async.whenData((value3) { | |
| return (value1 && value2) || value3; | |
| }); | |
| }); | |
| }); | |
| }); | |
| // ISSUE: how to properly combine async values without synthetic loading states? | |
| // when one is loading, don't continue execution of create and state should be loading | |
| // when one is error, don't continue execution of create and state should be the error | |
| // final correctCombineProvider = Provider<AsyncValue<bool>>((ref) { // or maybe FutureProvider<bool> | |
| // final value1Async = ref.watch(streamProvider1); | |
| // final value2Async = ref.watch(streamProvider2); | |
| // final value3Async = ref.watch(streamProvider3); | |
| // return (value1Async && value2Async) || value3Async; // ? | |
| // }); | |
| // and with family providers as well? | |
| // final correctFamilyCombineProvider = Provider<AsyncValue<bool>>((ref) { | |
| // final value1Async = ref.watch(streamProvider1); | |
| // final value2Async = ref.watch(streamProvider2(value1)); // ? | |
| // final value3Async = ref.watch(streamProvider3(value2)); // ? | |
| // return (value1Async && value2Async) || value3Async; // ? | |
| // }); | |
| // unwrap solution: throwing loading states and errors | |
| // extension workaround experiment | |
| class LoadingState {} | |
| extension Unwrap<T> on AsyncValue<T> { | |
| T unwrap() => when( | |
| data: (d) => d, | |
| error: (e, s) => Error.throwWithStackTrace(e, s), | |
| loading: () => throw LoadingState(), | |
| ); | |
| } | |
| Provider<AsyncValue<StateT>> asyncValueProvider<StateT>( | |
| StateT Function(Ref) create, | |
| ) => Provider((ref) { | |
| try { | |
| return AsyncData(create(ref)); | |
| } | |
| // should preserve previous state below, must be done in riverpod internally | |
| on LoadingState { | |
| return AsyncLoading(); | |
| } catch (e, s) { | |
| return AsyncError(e, s); | |
| } | |
| }); | |
| // use unwrap to combine async values without synthetic loading states | |
| final andUnwrapProvider = asyncValueProvider<bool>((ref) { | |
| final asyncValueA = ref.watch(streamProvider1); | |
| final asyncValueB = ref.watch(streamProvider2); | |
| final asyncValueC = ref.watch(streamProvider3); | |
| return (asyncValueA.unwrap() && asyncValueB.unwrap()) || asyncValueC.unwrap(); | |
| }); | |
| void main() { | |
| final container = ProviderContainer(); | |
| // change provider to see the different behaviors | |
| final provider = andUnwrapProvider; | |
| container.listen(provider, (_, state) => print(state), fireImmediately: true); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment