Skip to content

Instantly share code, notes, and snippets.

@TekExplorer
Created February 22, 2023 06:35
Show Gist options
  • Save TekExplorer/4800a677a5a01ed12f87f6d2ec4ca9fa to your computer and use it in GitHub Desktop.
Save TekExplorer/4800a677a5a01ed12f87f6d2ec4ca9fa to your computer and use it in GitHub Desktop.
an AsyncValue clone for mutations, i think.
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