// ignore_for_file: unreachable_switch_case import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'async_mutation.freezed.dart'; // TODO: test and improve @freezed class AsyncMutation with _$AsyncMutation { const AsyncMutation._(); const factory AsyncMutation.idle() = Idle; const factory AsyncMutation.inProgress() = InProgress; const factory AsyncMutation.fail(Object error, StackTrace stackTrace) = Fail; const factory AsyncMutation.success() = Success; bool get isInProgress => this is InProgress; } /// temporary(and hacky) way to manage async mutations ({ AsyncMutation state, ValueChanged> setFuture, ValueChanged setOnFailure, }) useMutation({ void Function(Object error)? initialOnFailure, void Function()? initialOnSuccess, }) { final mutationUpdate = useState?>(null); final mutationState = useFuture(mutationUpdate.value); final failureCallBack = useValueNotifier(initialOnFailure); final successCallBack = useValueNotifier(initialOnSuccess); final mapped = useMemoized( () { return switch (mutationState.connectionState) { ConnectionState.none => const Idle(), ConnectionState.waiting => const InProgress(), _ => mutationState.hasError ? Fail(mutationState.error!, mutationState.stackTrace!) : const Success(), }; }, [mutationState], ); // one-of callback in failure useMemoized( () { if (mapped case Fail(:final error)) { // if callback tries to build widget(show snackbar for example) this will prevent exceptions WidgetsBinding.instance.addPostFrameCallback( (_) => failureCallBack.value?.call(error), ); } if (mapped case Success()) { WidgetsBinding.instance.addPostFrameCallback( (_) => successCallBack.value?.call(), ); } }, [mapped, failureCallBack.value, successCallBack.value], ); return ( state: mapped, setFuture: (future) => mutationUpdate.value = future, setOnFailure: (onFailure) => failureCallBack.value = onFailure, ); }