Created
February 22, 2023 06:35
-
-
Save TekExplorer/4800a677a5a01ed12f87f6d2ec4ca9fa to your computer and use it in GitHub Desktop.
an AsyncValue clone for mutations, i think.
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 'package:meta/meta.dart'; | |
import 'package:riverpod_annotation/riverpod_annotation.dart'; | |
import 'package:stack_trace/stack_trace.dart'; | |
part 'mutation_provider.g.dart'; | |
// generic mutation | |
@riverpod | |
MutationState<void> mutation(MutationRef ref, Object mutationKey) { | |
return MutationState._initial(ref); | |
} | |
@immutable | |
abstract class MutationState<T> { | |
const MutationState._(this._ref); | |
const factory MutationState._error( | |
ProviderRef<MutationState<T>> _ref, | |
Object error, { | |
required StackTrace stackTrace, | |
}) = MutationError<T>._; | |
const factory MutationState._data( | |
ProviderRef<MutationState<T>> _ref, T value) = MutationData<T>._; | |
const factory MutationState._loading(ProviderRef<MutationState<T>> _ref) = | |
MutationLoading<T>._; | |
const factory MutationState._initial(ProviderRef<MutationState<T>> _ref) = | |
MutationInitial<T>._; | |
static Future<MutationState<T>> _guard<T>( | |
ProviderRef<MutationState<T>> _ref, | |
Future<T> Function() cb, | |
) async { | |
try { | |
return MutationState._data(_ref, await cb()); | |
} catch (e, s) { | |
return MutationState._error(_ref, e, stackTrace: s); | |
} | |
} | |
final ProviderRef<MutationState<T>> _ref; | |
bool get isLoading; | |
bool get hasValue; | |
T? get value; | |
Object? get error; | |
StackTrace? get stackTrace; | |
// final Future<MutationValue<T>> Function() call; | |
void _setState(MutationState<T> state) { | |
_ref.state = state.copyWithPrevious(_ref.state); | |
} | |
Future<MutationState<T>> run(Future<T> Function() cb) async { | |
_setState(MutationState._loading(_ref)); | |
final result = await MutationState._guard<T>(_ref, cb); | |
_setState(result); | |
return result; | |
} | |
R map<R>({ | |
required R Function(MutationInitial<T> initial) initial, | |
required R Function(MutationData<T> data) data, | |
required R Function(MutationError<T> error) error, | |
required R Function(MutationLoading<T> loading) loading, | |
}); | |
MutationState<T> copyWithPrevious(MutationState<T> previous); | |
MutationState<T> unwrapPrevious() { | |
return map( | |
initial: (i) => MutationInitial<T>._(_ref), | |
data: (d) { | |
if (d.isLoading) return MutationLoading<T>._(_ref); | |
return MutationData._(_ref, d.value!); | |
}, | |
error: (e) { | |
if (e.isLoading) return MutationLoading._(_ref); | |
return MutationError._(_ref, e.error, stackTrace: e.stackTrace); | |
}, | |
loading: (l) => MutationLoading<T>._(_ref), | |
); | |
} | |
@override | |
String toString() { | |
final content = [ | |
if (isLoading && this is! MutationLoading) 'isLoading: $isLoading', | |
if (hasValue) 'value: $value', | |
if (hasError) ...[ | |
'error: $error', | |
'stackTrace: $stackTrace', | |
], | |
if (stackTrace != null) 'stackTrace: $stackTrace', | |
].join(', '); | |
return '$runtimeType($content)'; | |
} | |
@override | |
bool operator ==(Object other) { | |
return runtimeType == other.runtimeType && | |
other is MutationState<T> && | |
other.isInitial == isInitial && | |
other.isLoading == isLoading && | |
other.hasValue == hasValue && | |
other.error == error && | |
other.stackTrace == stackTrace && | |
other.valueOrNull == valueOrNull; | |
} | |
@override | |
int get hashCode => Object.hash( | |
runtimeType, | |
isLoading, | |
hasValue, | |
// Fallback null values to 0, making sure Object.hash hashes all values | |
valueOrNull ?? 0, | |
error ?? 0, | |
stackTrace ?? 0, | |
); | |
} | |
class MutationInitial<T> extends MutationState<T> { | |
const MutationInitial._(super._ref) : super._(); | |
@override | |
T? get value => null; | |
@override | |
bool get hasValue => false; | |
@override | |
bool get isLoading => false; | |
@override | |
StackTrace? get stackTrace => null; | |
@override | |
Object? get error => null; | |
@override | |
R map<R>( | |
{required R Function(MutationInitial<T> initial) initial, | |
required R Function(MutationData<T> data) data, | |
required R Function(MutationError<T> error) error, | |
required R Function(MutationLoading<T> loading) loading}) { | |
return initial(this); | |
} | |
@override | |
MutationState<T> copyWithPrevious(MutationState<T> previous) { | |
// We shouldn't even have a previous value if we are initial | |
return this; | |
} | |
} | |
class MutationLoading<T> extends MutationState<T> { | |
const MutationLoading._(super._ref) | |
: hasValue = false, | |
value = null, | |
error = null, | |
stackTrace = null, | |
super._(); | |
const MutationLoading.__( | |
super._ref, { | |
required this.hasValue, | |
required this.value, | |
required this.error, | |
required this.stackTrace, | |
}) : super._(); | |
@override | |
bool get isLoading => true; | |
@override | |
final bool hasValue; | |
@override | |
final T? value; | |
@override | |
final Object? error; | |
@override | |
final StackTrace? stackTrace; | |
@override | |
R map<R>({ | |
required R Function(MutationInitial<T> initial) initial, | |
required R Function(MutationData<T> data) data, | |
required R Function(MutationError<T> error) error, | |
required R Function(MutationLoading<T> loading) loading, | |
}) { | |
return loading(this); | |
} | |
@override | |
MutationState<T> copyWithPrevious( | |
MutationState<T> previous, { | |
bool isRefresh = true, | |
}) { | |
if (isRefresh) { | |
return previous.map( | |
initial: (_) => this, | |
data: (d) => MutationData.__( | |
_ref, | |
d.value, | |
isLoading: true, | |
error: d.error, | |
stackTrace: d.stackTrace, | |
), | |
error: (e) => MutationError.__( | |
_ref, | |
e.error, | |
isLoading: true, | |
value: e.valueOrNull, | |
stackTrace: e.stackTrace, | |
hasValue: e.hasValue, | |
), | |
loading: (_) => this, | |
); | |
} else { | |
return previous.map( | |
initial: (_) => this, | |
data: (e) => MutationLoading.__( | |
_ref, | |
hasValue: true, | |
value: e.valueOrNull, | |
error: e.error, | |
stackTrace: e.stackTrace, | |
), | |
error: (e) => MutationLoading.__( | |
_ref, | |
hasValue: e.hasValue, | |
value: e.valueOrNull, | |
error: e.error, | |
stackTrace: e.stackTrace, | |
), | |
loading: (e) => e, | |
); | |
} | |
} | |
} | |
class MutationData<T> extends MutationState<T> { | |
const MutationData._(super._ref, this.value) | |
: isLoading = false, | |
error = null, | |
stackTrace = null, | |
super._(); | |
const MutationData.__( | |
super._ref, | |
this.value, { | |
required this.isLoading, | |
required this.error, | |
required this.stackTrace, | |
}) : super._(); | |
@override | |
final T value; | |
@override | |
bool get hasValue => true; | |
@override | |
final bool isLoading; | |
@override | |
final Object? error; | |
@override | |
final StackTrace? stackTrace; | |
@override | |
R map<R>({ | |
required R Function(MutationInitial<T> initial) initial, | |
required R Function(MutationData<T> data) data, | |
required R Function(MutationError<T> error) error, | |
required R Function(MutationLoading<T> loading) loading, | |
}) { | |
return data(this); | |
} | |
@override | |
MutationState<T> copyWithPrevious(MutationState<T> previous) => this; | |
} | |
class MutationError<T> extends MutationState<T> { | |
const MutationError.__( | |
super._ref, | |
this.error, { | |
required this.stackTrace, | |
required this.isLoading, | |
required T? value, | |
required this.hasValue, | |
}) : _value = value, | |
super._(); | |
const MutationError._(super._ref, this.error, {required this.stackTrace}) | |
: isLoading = false, | |
hasValue = false, | |
_value = null, | |
super._(); | |
@override | |
final bool isLoading; | |
@override | |
final bool hasValue; | |
@override | |
T? get value { | |
if (!hasValue) { | |
throwErrorWithCombinedStackTrace(error, stackTrace); | |
} | |
return _value; | |
} | |
final T? _value; | |
@override | |
final Object error; | |
@override | |
final StackTrace stackTrace; | |
@override | |
R map<R>({ | |
required R Function(MutationInitial<T> initial) initial, | |
required R Function(MutationData<T> data) data, | |
required R Function(MutationError<T> error) error, | |
required R Function(MutationLoading<T> loading) loading, | |
}) { | |
return error(this); | |
} | |
@override | |
MutationState<T> copyWithPrevious(MutationState<T> previous) { | |
return MutationError.__( | |
_ref, | |
error, | |
stackTrace: stackTrace, | |
isLoading: isLoading, | |
value: previous.valueOrNull, | |
hasValue: previous.hasValue, | |
); | |
} | |
} | |
extension MutationValueX<T> on MutationState<T> { | |
bool get isInitial => this is MutationInitial; | |
T get requireValue { | |
if (hasValue) return value as T; | |
if (hasError) throwErrorWithCombinedStackTrace(error!, stackTrace!); | |
throw StateError( | |
'Tried to call `requireValue` on a `MutationValue` that has no value: $this', | |
); | |
} | |
T? get valueOrNull => hasValue ? value : null; | |
bool get isRerunning => | |
isLoading && (hasValue || hasError) && this is! AsyncLoading; | |
bool get hasError => error != null; | |
MutationData<T>? get asData => maybeMap(data: (d) => d, orElse: () => null); | |
MutationError<T>? get asError => | |
maybeMap(error: (e) => e, orElse: () => null); | |
R maybeWhen<R>({ | |
bool skipLoadingOnRerun = false, | |
bool skipError = false, | |
R Function()? initial, | |
R Function(T value)? data, | |
R Function(Object error, StackTrace stackTrace)? error, | |
R Function()? loading, | |
required R Function() orElse, | |
}) { | |
return when( | |
skipError: skipError, | |
skipLoadingOnRerun: skipLoadingOnRerun, | |
initial: initial ?? orElse, | |
loading: loading ?? orElse, | |
data: (d) { | |
if (data != null) return data(d); | |
return orElse(); | |
}, | |
error: (e, s) { | |
if (error != null) return error(e, s); | |
return orElse(); | |
}, | |
); | |
} | |
R when<R>({ | |
bool skipLoadingOnRerun = false, | |
bool skipError = false, | |
required R Function() initial, | |
required R Function(T data) data, | |
required R Function(Object error, StackTrace stackTrace) error, | |
required R Function() loading, | |
}) { | |
if (isInitial) { | |
return initial(); | |
} | |
if (isLoading) { | |
bool skip; | |
if (isRerunning) { | |
skip = skipLoadingOnRerun; | |
} else { | |
skip = false; | |
} | |
if (!skip) return loading(); | |
} | |
if (hasError && (!hasValue || !skipError)) { | |
return error(this.error!, stackTrace!); | |
} | |
return data(requireValue); | |
} | |
R? whenOrNull<R>({ | |
bool skipLoadingOnRerun = false, | |
bool skipError = false, | |
R? Function()? initial, | |
R? Function(T data)? data, | |
R? Function(Object error, StackTrace stackTrace)? error, | |
R? Function()? loading, | |
}) { | |
return when( | |
skipError: skipError, | |
skipLoadingOnRerun: skipLoadingOnRerun, | |
initial: initial ?? () => null, | |
data: data ?? (_) => null, | |
error: error ?? (err, stack) => null, | |
loading: loading ?? () => null, | |
); | |
} | |
R maybeMap<R>({ | |
R Function(MutationInitial<T> initial)? initial, | |
R Function(MutationData<T> data)? data, | |
R Function(MutationError<T> error)? error, | |
R Function(MutationLoading<T> loading)? loading, | |
required R Function() orElse, | |
}) { | |
return map( | |
initial: (i) { | |
if (initial != null) return initial(i); | |
return orElse(); | |
}, | |
data: (d) { | |
if (data != null) return data(d); | |
return orElse(); | |
}, | |
error: (e) { | |
if (error != null) return error(e); | |
return orElse(); | |
}, | |
loading: (l) { | |
if (loading != null) return loading(l); | |
return orElse(); | |
}, | |
); | |
} | |
} | |
Never throwErrorWithCombinedStackTrace(Object error, StackTrace stackTrace) { | |
final chain = Chain([ | |
Trace.current(), | |
...Chain.forTrace(stackTrace).traces, | |
]).foldFrames((frame) => frame.package == 'riverpod'); | |
Error.throwWithStackTrace(error, chain); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment