import 'dart:async'; import 'package:fl_query/base_operation.dart'; import 'package:fl_query/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, V); class Mutation extends BaseOperation { // all params final String mutationKey; MutationTaskFunction task; @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 Duration cacheTime, MutationListener? onData, MutationListener? onError, MutationListener? onMutate, }) : super(cacheTime: cacheTime, status: MutationStatus.idle) { 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, }) : mutationKey = options.mutationKey, task = options.task, super( retries: options.retries ?? 3, retryDelay: options.retryDelay ?? const Duration(milliseconds: 200), cacheTime: options.cacheTime ?? const Duration(minutes: 5), status: MutationStatus.idle, ) { 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; @override bool get isError => status == MutationStatus.error; @override bool get isIdle => status == MutationStatus.idle; @override bool get isLoading => status == MutationStatus.loading; @override bool get isSuccess => status == MutationStatus.success; }