import 'dart:async'; import 'package:fl_query/src/base_operation.dart'; import 'package:fl_query/src/models/mutation_job.dart'; import 'package:flutter/widgets.dart'; enum MutationStatus { error, success, loading, idle, } typedef MutationListener = FutureOr Function(T); typedef MutationTaskFunction = FutureOr Function( String queryKey, V variables); class Mutation extends BaseOperation { // all params final String mutationKey; MutationTaskFunction task; MutationStatus status; @protected final Set> onDataListeners = {}; @protected final Set> onErrorListeners = {}; @protected final Set> onMutateListeners = {}; Mutation({ required this.mutationKey, required this.task, required super.retries, required super.retryDelay, required super.queryBowl, required Duration cacheTime, MutationListener? onData, MutationListener? onError, MutationListener? onMutate, }) : status = MutationStatus.idle, super(cacheTime: cacheTime) { if (onData != null) onDataListeners.add(onData); if (onError != null) onErrorListeners.add(onError); if (onMutate != null) onMutateListeners.add(onMutate); } Mutation.fromOptions( MutationJob options, { MutationListener? onData, MutationListener? onError, MutationListener? onMutate, required super.queryBowl, }) : mutationKey = options.mutationKey, task = options.task, status = MutationStatus.idle, super( retries: options.retries ?? 3, retryDelay: options.retryDelay ?? const Duration(milliseconds: 200), cacheTime: options.cacheTime ?? const Duration(minutes: 5), ) { if (onData != null) onDataListeners.add(onData); if (onError != null) onErrorListeners.add(onError); } // all methods /// Calls the task function & doesn't check if there's already /// cached data available Future _execute(V variables) async { try { status = MutationStatus.loading; notifyListeners(); retryAttempts = 0; for (final onMutate in onMutateListeners) { onMutate(variables); } data = await task(mutationKey, variables); updatedAt = DateTime.now(); status = MutationStatus.success; for (final onData in onDataListeners) { onData(data!); } notifyListeners(); } catch (e) { if (retries == 0) { status = MutationStatus.error; error = e; for (final onError in onErrorListeners) { onError(error); } notifyListeners(); } else { // retrying for retry count if failed for the first time while (retryAttempts <= retries) { await Future.delayed(retryDelay); try { for (final onMutate in onMutateListeners) { onMutate(variables); } data = await task(mutationKey, variables); status = MutationStatus.success; for (final onData in onDataListeners) { onData(data!); } notifyListeners(); break; } catch (e) { if (retryAttempts == retries) { status = MutationStatus.error; error = e; for (final onError in onErrorListeners) { onError(error); } notifyListeners(); } retryAttempts++; } } } } } void mutate( V variables, { MutationListener? onData, MutationListener? onError, }) { if (onData != null) onDataListeners.add(onData); if (onError != null) onErrorListeners.add(onError); _execute(variables).then((_) { onDataListeners.remove(onData); onErrorListeners.remove(onError); }); } Future mutateAsync(V variables) async { return await _execute(variables).then((_) => data); } /// Update configurations of the mutation after already creating the /// Mutation instance void updateDefaultOptions({ Duration? cacheTime, }) { if (this.cacheTime == Duration(minutes: 5) && cacheTime != null) this.cacheTime = cacheTime; notifyListeners(); } void reset() { data = null; retryAttempts = 0; updatedAt = DateTime.now(); onDataListeners.clear(); onErrorListeners.clear(); status = MutationStatus.idle; onMutateListeners.clear(); } A? cast() => this is A ? this as A : null; bool get isError => status == MutationStatus.error; bool get isIdle => status == MutationStatus.idle; bool get isLoading => status == MutationStatus.loading; bool get isSuccess => status == MutationStatus.success; @override bool operator ==(other) { return (other is Mutation && other.mutationKey == mutationKey) || identical(other, this); } }