Initial Support

This commit is contained in:
Kingkor Roy Tirtho
2022-05-27 12:51:13 +06:00
parent b5a73f8cae
commit 64c852fddc
162 changed files with 4691 additions and 5236 deletions
+17 -2
View File
@@ -1,4 +1,19 @@
library fl_query;
export 'package:fl_query/src/core/core.dart';
// export 'package:fl_query/src/core/framework.dart';
import 'package:flutter/material.dart';
class FlQueryScope extends StatefulWidget {
final Widget child;
const FlQueryScope({required this.child, Key? key}) : super(key: key);
@override
State<FlQueryScope> createState() => _FlQueryScopeState();
}
class _FlQueryScopeState extends State<FlQueryScope> {
@override
Widget build(BuildContext context) {
Theme.of(context);
return widget.child;
}
}
+108
View File
@@ -0,0 +1,108 @@
import 'dart:async';
import 'package:flutter/widgets.dart';
enum QueryStatus {
failed,
succeed,
pending,
refetching;
}
typedef QueryTaskFunction<T> = FutureOr<T> Function(String);
typedef QueryListener<T> = FutureOr<void> Function(T);
typedef ListenerUnsubscriber = void Function();
class Query<T> extends ChangeNotifier {
// all params
final String queryKey;
QueryTaskFunction<T> task;
final int retries;
final Duration retryDelay;
// all properties
T? data;
dynamic error;
QueryStatus status;
int retryAttempts = 0;
late DateTime updatedAt;
int refetchCount = 0;
@protected
bool fetched = false;
final QueryListener<T>? _onData;
final QueryListener<dynamic>? _onError;
Query({
required this.queryKey,
required this.task,
this.retries = 3,
this.retryDelay = const Duration(milliseconds: 200),
T? initialData,
QueryListener<T>? onData,
QueryListener<dynamic>? onError,
}) : status = QueryStatus.pending,
data = initialData,
_onData = onData,
_onError = onError;
// all getters & setters
bool get hasData => data != null && error == null;
bool get hasError =>
status == QueryStatus.failed && error != null && data == null;
bool get isLoading =>
status == QueryStatus.pending && data == null && error == null;
bool get isRefetching =>
status == QueryStatus.refetching && data == null && error == null;
bool get isSucceeded => status == QueryStatus.succeed && data != null;
// all methods
Future<void> _execute({bool isFetch = true}) async {
try {
retryAttempts = 0;
status = isFetch ? QueryStatus.pending : QueryStatus.refetching;
data = await task(queryKey);
updatedAt = DateTime.now();
status = QueryStatus.succeed;
_onData?.call(data!);
notifyListeners();
} catch (e) {
status = QueryStatus.failed;
error = e;
_onError?.call(e);
notifyListeners();
// retrying for retry count if failed for the first time
while (retryAttempts <= retries) {
await Future.delayed(retryDelay);
try {
data = await task(queryKey);
status = QueryStatus.succeed;
_onData?.call(data!);
notifyListeners();
break;
} catch (e) {
status = QueryStatus.failed;
error = e;
retryAttempts++;
_onError?.call(e);
notifyListeners();
}
}
}
}
Future<T?> fetch() async {
return _execute().then((_) {
fetched = true;
return data;
});
}
Future<T?> refetch() {
refetchCount++;
return _execute(isFetch: false).then((_) => data);
}
}
+98
View File
@@ -0,0 +1,98 @@
import 'package:fl_query/query.dart';
import 'package:collection/collection.dart';
import 'package:flutter/widgets.dart';
class QueryBowlScope extends StatefulWidget {
final Widget child;
final Duration? staleTime;
const QueryBowlScope({
required this.child,
this.staleTime,
Key? key,
}) : super(key: key);
@override
State<QueryBowlScope> createState() => _QueryBowlScopeState();
}
class _QueryBowlScopeState extends State<QueryBowlScope> {
late Set<Query> queries;
@override
void initState() {
super.initState();
queries = {};
}
void updateQueries() {
setState(() {
queries = Set.from(queries);
});
}
@override
Widget build(BuildContext context) {
return QueryBowl(
onUpdate: updateQueries,
queries: queries,
child: widget.child,
);
}
}
class QueryBowl extends InheritedWidget {
final Set<Query> queries;
final Duration staleTime;
final void Function() onUpdate;
const QueryBowl({
required Widget child,
required this.onUpdate,
required this.queries,
this.staleTime = const Duration(minutes: 5),
Key? key,
}) : super(child: child, key: key);
listenToQueryUpdate() {
for (final query in queries) {
query.addListener(onUpdate);
}
}
void disposeListeners() {
for (final query in queries) {
query.removeListener(onUpdate);
}
}
Future<T?> fetchQuery<T>(Query<T> query) async {
final prevQuery =
queries.firstWhereOrNull((q) => q.queryKey == query.queryKey);
if (prevQuery is Query<T>) {
if (!prevQuery.hasData) {
return prevQuery.fetched
? await prevQuery.refetch()
: await prevQuery.fetch();
}
return prevQuery.data;
}
queries.add(query);
disposeListeners();
listenToQueryUpdate();
return await query.fetch();
}
Query<T>? getQuery<T>(String queryKey) {
return queries.firstWhereOrNull(
(query) => query.queryKey == queryKey && query is Query<T>)
as Query<T>?;
}
static QueryBowl of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<QueryBowl>()!;
@override
bool updateShouldNotify(QueryBowl oldWidget) {
return oldWidget.staleTime != staleTime || oldWidget.queries != queries;
}
}
+36
View File
@@ -0,0 +1,36 @@
import 'package:fl_query/query.dart';
import 'package:fl_query/query_bowl.dart';
import 'package:flutter/widgets.dart';
class QueryBuilder<T> extends StatefulWidget {
final Widget Function(BuildContext, Query<T>) builder;
final QueryTaskFunction<T> task;
final String queryKey;
const QueryBuilder({
required this.builder,
required this.task,
required this.queryKey,
Key? key,
}) : super(key: key);
@override
State<QueryBuilder<T>> createState() => _QueryBuilderState<T>();
}
class _QueryBuilderState<T> extends State<QueryBuilder<T>> {
late Query<T> query;
@override
void initState() {
super.initState();
query = Query<T>(queryKey: widget.queryKey, task: widget.task);
WidgetsBinding.instance.addPostFrameCallback((_) async {
await QueryBowl.of(context).fetchQuery(query);
});
}
@override
Widget build(BuildContext context) {
final queryRT = QueryBowl.of(context).getQuery<T>(widget.queryKey) ?? query;
return widget.builder(context, queryRT);
}
}
-21
View File
@@ -1,21 +0,0 @@
export 'package:fl_query/src/core/retryer.dart' show CancelledError;
export 'package:fl_query/src/core/query_cache.dart' show QueryCache;
export 'package:fl_query/src/core/query_client.dart' show QueryClient;
export 'package:fl_query/src/core/query_observer.dart' show QueryObserver;
export 'package:fl_query/src/core/query_key.dart';
// export 'package:fl_query/src/core/queriesObserver.dart' show QueriesObserver;
// export 'package:fl_query/src/core/infiniteQueryObserver.dart' show InfiniteQueryObserver;
// export 'package:fl_query/src/core/mutationCache.dart' show MutationCache;
// export 'package:fl_query/src/core/mutationObserver.dart' show MutationObserver;
// export 'package:fl_query/src/core/logger.dart' show setLogger;
export 'package:fl_query/src/core/notify_manager.dart' show notifyManager;
// export 'package:fl_query/src/core/focusManager.dart' show focusManager;
export 'package:fl_query/src/core/online_manager.dart' show onlineManager;
export 'package:fl_query/src/core/utils.dart' show hashQueryKey;
export 'package:fl_query/src/core/retryer.dart' show isCancelledError;
// export 'package:fl_query/src/core/hydration.dart' show dehydrate, DehydrateOptions, DehydratedState, HydrateOptions, ShouldDehydrateMutationFunction, ShouldDehydrateQueryFunction;
export 'package:fl_query/src/core/models.dart';
export 'package:fl_query/src/core/query.dart' show Query;
// export type { Mutation } from './mutation'
// export type { Logger } from './logger'
-596
View File
@@ -1,596 +0,0 @@
import 'dart:async';
import 'package:fl_query/src/core/query.dart';
import 'package:fl_query/src/core/query_key.dart';
import 'package:fl_query/src/core/retryer.dart';
typedef QueryMeta<T> = Map<String, T>;
typedef QueryKeyHashFunction = String Function(QueryKey queryKey);
typedef QueryFunction<T, TPageParam> = FutureOr<T> Function(
QueryFunctionContext<TPageParam> context,
);
typedef GetPreviousPageParamFunction<TQueryFnData extends Map<String, dynamic>>
= Function(
TQueryFnData firstPage,
List<TQueryFnData> allPages,
);
typedef GetNextPageParamFunction<TQueryFnData extends Map<String, dynamic>>
= Function(
TQueryFnData firstPage,
List<TQueryFnData> allPages,
);
class QueryOptions<TQueryFnData extends Map<String, dynamic>, TError,
TData extends Map<String, dynamic>> {
ShouldRetryFunction<TError>? retry;
RetryDelayFunction<TError>? retryDelay;
Duration? cacheTime;
bool Function(TData? oldData, TData newData)? isDataEqual;
QueryFunction<TQueryFnData, dynamic>? queryFn;
QueryKey? queryKey;
/// Basically [QueryKey.key] in short form
String? queryHash;
QueryKeyHashFunction? queryKeyHashFn;
TData? initialData;
DateTime? initialDataUpdatedAt;
QueryBehavior<TQueryFnData, TError, TData>? behavior;
/// Set this to `false` to disable structural sharing between query results\
/// Defaults to `true`.
bool? structuralSharing;
/// This function can be set to automatically get the previous cursor for infinite queries.
/// The result will also be used to determine the value of `hasPreviousPage`.
GetPreviousPageParamFunction<TQueryFnData>? getPreviousPageParam;
/// This function can be set to automatically get the next cursor for
/// infinite queries.
/// The result will also be used to determine the value of
/// `hasNextPage`.
GetNextPageParamFunction<TQueryFnData>? getNextPageParam;
bool? defaulted;
/// Additional payload to be stored on each query.
/// Use this property to pass information that can be used in other places.
QueryMeta? meta;
QueryOptions({
this.retry,
this.retryDelay,
this.queryKey,
this.queryKeyHashFn,
this.cacheTime,
this.isDataEqual,
this.queryFn,
this.defaulted,
this.initialData,
this.initialDataUpdatedAt,
this.meta,
this.queryHash,
this.structuralSharing,
this.getPreviousPageParam,
this.getNextPageParam,
this.behavior,
});
QueryOptions.fromJson(Map<String, dynamic> json) {
queryKey = json["queryKey"];
queryKeyHashFn = json["queryKeyHashFn"];
cacheTime = json["cacheTime"];
isDataEqual = json["isDataEqual"];
queryFn = json["queryFn"];
queryHash = json["queryHash"];
initialData = json["initialData"];
initialDataUpdatedAt = json["initialDataUpdatedAt"];
meta = json["meta"];
structuralSharing = json["structuralSharing"];
defaulted = json["defaulted"];
retry = json["retry"];
retryDelay = json["retryDelay"];
behavior = json["behavior"];
getPreviousPageParam = json["getPreviousPageParam"];
getNextPageParam = json["getNextPageParam"];
}
Map<String, dynamic> toJson() {
return {
"queryKey": queryKey,
"queryKeyHashFn": queryKeyHashFn,
"cacheTime": cacheTime,
"isDataEqual": isDataEqual,
"queryFn": queryFn,
"queryHash": queryHash,
"initialData": initialData,
"initialDataUpdatedAt": initialDataUpdatedAt,
"meta": meta,
"structuralSharing": structuralSharing,
"defaulted": defaulted,
"retry": retry,
"retryDelay": retryDelay,
"behavior": behavior,
"getPreviousPageParam": getPreviousPageParam,
"getNextPageParam": getNextPageParam,
};
}
}
class QueryFilters {
bool? active;
bool? exact;
bool? inactive;
bool Function(Query query)? predicate;
QueryKey? queryKey;
bool? stale;
bool? fetching;
QueryFilters({
this.active,
this.exact,
this.inactive,
this.predicate,
this.queryKey,
this.stale,
this.fetching,
});
Map<String, dynamic> toJson() {
return {
"active": active,
"exact": exact,
"inactive": inactive,
"queryKey": queryKey,
"stale": stale,
"fetching": fetching,
"predicate": predicate,
};
}
}
class RefetchPageFilters<TPageData> {
bool Function(TPageData lastPage, int index, List<TPageData> allPages)?
refetchPage;
}
class RefetchableQueryFilters<TPageData> extends QueryFilters
implements RefetchPageFilters<TPageData> {
@override
bool Function(TPageData lastPage, int index, List<TPageData> allPages)?
refetchPage;
RefetchableQueryFilters({
bool? active,
bool? exact,
bool? inactive,
bool Function(Query query)? predicate,
QueryKey? queryKey,
bool? stale,
bool? fetching,
this.refetchPage,
}) : super(
active: active,
exact: exact,
fetching: fetching,
inactive: inactive,
predicate: predicate,
queryKey: queryKey,
stale: stale,
);
RefetchableQueryFilters.fromJson(Map<String, dynamic> json) {
active = json["active"];
exact = json["exact"];
inactive = json["inactive"];
queryKey = json["queryKey"];
stale = json["stale"];
fetching = json["fetching"];
predicate = json["predicate"];
refetchPage = json["refetchPage"];
}
@override
Map<String, dynamic> toJson() {
return {
"active": active,
"exact": exact,
"inactive": inactive,
"queryKey": queryKey,
"stale": stale,
"fetching": fetching,
"predicate": predicate,
"refetchPage": refetchPage,
};
}
}
class InvalidateQueryFilters<TPageData>
extends RefetchableQueryFilters<TPageData> {
bool? refetchActive;
bool? refetchInactive;
InvalidateQueryFilters({
bool? active,
bool? exact,
bool? inactive,
bool Function(Query query)? predicate,
QueryKey? queryKey,
bool? stale,
bool? fetching,
bool Function(TPageData lastPage, int index, List<TPageData> allPages)?
refetchPage,
this.refetchActive,
this.refetchInactive,
}) : super(
active: active,
exact: exact,
fetching: fetching,
inactive: inactive,
predicate: predicate,
queryKey: queryKey,
stale: stale,
refetchPage: refetchPage,
);
InvalidateQueryFilters.fromJson(Map<String, dynamic> json)
: super.fromJson(json) {
refetchActive = json["refetchActive"];
refetchInactive = json["refetchInactive"];
}
@override
Map<String, dynamic> toJson() {
return {
...super.toJson(),
"refetchActive": refetchActive,
"refetchInactive": refetchInactive,
};
}
}
class RefetchOptions {
bool? throwOnError;
bool? cancelRefetch;
RefetchOptions({
this.cancelRefetch,
this.throwOnError,
});
}
enum QueryStatus {
idle,
loading,
error,
success,
}
class QueryObserverResult<TData extends Map<String, dynamic>, TError> {
TData? data;
DateTime? dataUpdatedAt;
TError? error;
DateTime? errorUpdatedAt;
int failureCount;
bool isError;
bool isFetched;
bool isFetchedAfterMount;
bool isFetching;
bool isIdle;
bool isLoading;
bool isLoadingError;
bool isPlaceholderData;
bool isPreviousData;
bool isRefetchError;
bool isRefetching;
bool isStale;
bool isSuccess;
Future<QueryObserverResult<TData, TError>?> Function<TPageData>({
RefetchOptions options,
RefetchableQueryFilters<TPageData> filters,
}) refetch;
void Function() remove;
QueryStatus status;
QueryObserverResult({
required this.failureCount,
required this.isError,
required this.isFetched,
required this.isFetchedAfterMount,
required this.isFetching,
required this.isIdle,
required this.isLoading,
required this.isLoadingError,
required this.isPlaceholderData,
required this.isPreviousData,
required this.isRefetchError,
required this.isRefetching,
required this.isStale,
required this.isSuccess,
required this.refetch,
required this.remove,
required this.status,
this.data,
this.error,
this.dataUpdatedAt,
this.errorUpdatedAt,
}) {
String errorLabel =
"[QueryObserverResult.QueryObserverResult] status = `$status` but parent has wrong set of properties";
if (status == QueryStatus.idle &&
(data != null ||
error != null ||
isError ||
!isIdle ||
isLoading ||
isLoadingError ||
isRefetchError ||
isSuccess)) throw Exception(errorLabel);
if (status == QueryStatus.loading &&
(data != null ||
error != null ||
isError ||
isIdle ||
!isLoading ||
isLoadingError ||
isRefetchError ||
isSuccess != false)) throw Exception(errorLabel);
if (status == QueryStatus.error &&
((!(error is TError)) || !isError || isIdle || isLoading || isSuccess))
throw Exception(errorLabel);
if (status == QueryStatus.success &&
(!(data is TData) ||
error != null ||
isError ||
isIdle ||
isLoading ||
isLoadingError ||
isRefetchError ||
!isSuccess)) throw Exception(errorLabel);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = {
'data': this.data,
'dataUpdatedAt': dataUpdatedAt,
'error': error,
'errorUpdatedAt': errorUpdatedAt,
'failureCount': failureCount,
'isError': isError,
'isFetched': isFetched,
'isFetchedAfterMount': isFetchedAfterMount,
'isFetching': isFetching,
'isIdle': isIdle,
'isLoading': isLoading,
'isLoadingError': isLoadingError,
'isPlaceholderData': isPlaceholderData,
'isPreviousData': isPreviousData,
'isRefetchError': isRefetchError,
'isRefetching': isRefetching,
'isStale': isStale,
'isSuccess': isSuccess,
'refetch': refetch,
'remove': remove,
'status': status,
};
return data;
}
}
typedef RefetchIntervalFunction<
TQueryFnData extends Map<String, dynamic>,
TError,
TQueryData extends Map<String, dynamic>,
TData extends Map<String, dynamic>>
= Duration? Function(
TData? data,
Query<TQueryFnData, TError, TQueryData> query,
);
enum RefetchOnReconnect {
on,
off,
always,
}
enum RefetchOnMount {
on,
off,
always,
}
class QueryObserverOptions<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>,
TQueryData extends Map<String, dynamic>>
extends QueryOptions<TQueryFnData, TError, TQueryData> {
bool? enabled;
Duration? staleTime;
RefetchIntervalFunction<TQueryFnData, TError, TQueryData, TData>?
refetchInterval;
bool? refetchIntervalInBackground;
RefetchOnReconnect? refetchOnReconnect;
RefetchOnMount? refetchOnMount;
bool? retryOnMount;
OnData? onSuccess;
OnError? onError;
void Function(TData? data, [TError? error])? onSettled;
bool Function(TError error)? useErrorBoundary;
TData Function(TQueryData? data)? select;
bool? suspense;
bool? keepPreviousData;
TQueryData? placeholderData;
bool? optimisticResults;
/*List<String>|'tracked'?*/ dynamic notifyOnChangeProps;
List<String>? notifyOnChangePropsExclusions;
QueryObserverOptions({
this.enabled,
this.staleTime,
this.refetchInterval,
this.refetchIntervalInBackground,
this.refetchOnReconnect,
this.refetchOnMount,
this.retryOnMount,
this.onSuccess,
this.onError,
this.onSettled,
this.useErrorBoundary,
this.select,
this.suspense,
this.keepPreviousData,
this.placeholderData,
this.optimisticResults,
QueryKey? queryKey,
QueryKeyHashFunction? queryKeyHashFn,
Duration? cacheTime,
bool Function(TQueryData? oldData, TQueryData newData)? isDataEqual,
QueryFunction<TQueryFnData, dynamic>? queryFn,
String? queryHash,
TQueryData? initialData,
DateTime? initialDataUpdatedAt,
QueryMeta? meta,
bool? structuralSharing,
bool? defaulted,
ShouldRetryFunction<TError>? retry,
RetryDelayFunction<TError>? retryDelay,
QueryBehavior<TQueryFnData, TError, TQueryData>? behavior,
GetPreviousPageParamFunction<TQueryFnData>? getPreviousPageParam,
GetNextPageParamFunction<TQueryFnData>? getNextPageParam,
}) : super(
queryKey: queryKey,
queryKeyHashFn: queryKeyHashFn,
cacheTime: cacheTime,
isDataEqual: isDataEqual,
queryFn: queryFn,
queryHash: queryHash,
initialData: initialData,
initialDataUpdatedAt: initialDataUpdatedAt,
meta: meta,
structuralSharing: structuralSharing,
defaulted: defaulted,
behavior: behavior,
getNextPageParam: getNextPageParam,
getPreviousPageParam: getPreviousPageParam,
retry: retry,
retryDelay: retryDelay,
);
QueryObserverOptions.fromJson(Map<String, dynamic> json)
: enabled = json["enabled"],
staleTime = json["staleTime"],
refetchInterval = json["refetchInterval"],
refetchIntervalInBackground = json["refetchIntervalInBackground"],
refetchOnReconnect = json["refetchOnReconnect"],
refetchOnMount = json["refetchOnMount"],
retryOnMount = json["retryOnMount"],
onSuccess = json["onSuccess"],
onError = json["onError"],
onSettled = json["onSettled"],
useErrorBoundary = json["useErrorBoundary"],
select = json["select"],
suspense = json["suspense"],
keepPreviousData = json["keepPreviousData"],
placeholderData = json["placeholderData"],
optimisticResults = json["optimisticResults"],
super.fromJson(json);
@override
Map<String, dynamic> toJson() {
return {
...super.toJson(),
"enabled": enabled,
"staleTime": staleTime,
"refetchInterval": refetchInterval,
"refetchIntervalInBackground": refetchIntervalInBackground,
"refetchOnReconnect": refetchOnReconnect,
"refetchOnMount": refetchOnMount,
"retryOnMount": retryOnMount,
"onSuccess": onSuccess,
"onError": onError,
"onSettled": onSettled,
"useErrorBoundary": useErrorBoundary,
"select": select,
"suspense": suspense,
"keepPreviousData": keepPreviousData,
"placeholderData": placeholderData,
"optimisticResults": optimisticResults,
};
}
}
class QueryFunctionContext<TPageParam> {
QueryKey queryKey;
/* AbortSignal */ dynamic? signal;
TPageParam? pageParam;
QueryMeta? meta;
QueryFunctionContext({
required this.queryKey,
this.signal,
this.pageParam,
this.meta,
});
}
class DefaultOptions<TError> {
QueryObserverOptions<dynamic, TError, dynamic, dynamic>? queries;
// MutationObserverOptions<dynamic, TError, dynamic>? mutations;
DefaultOptions({
this.queries,
});
}
class FetchQueryOptions<TQueryFnData extends Map<String, dynamic>, TError,
TData extends Map<String, dynamic>>
extends QueryOptions<TQueryFnData, TError, TData> {
/// The time after data is considered stale.
/// If the data is fresh it will be returned from the cache.
Duration? staleTime;
FetchQueryOptions({
ShouldRetryFunction<TError>? retry,
RetryDelayFunction<TError>? retryDelay,
Duration? cacheTime,
bool Function(TData? oldData, TData newData)? isDataEqual,
QueryFunction<TQueryFnData, dynamic>? queryFn,
QueryKey? queryKey,
String? queryHash,
QueryKeyHashFunction? queryKeyHashFn,
TData? initialData,
DateTime? initialDataUpdatedAt,
QueryBehavior<TQueryFnData, TError, TData>? behavior,
bool? structuralSharing,
GetPreviousPageParamFunction<TQueryFnData>? getPreviousPageParam,
GetNextPageParamFunction<TQueryFnData>? getNextPageParam,
bool? defaulted,
this.staleTime,
}) : super(
retry: retry,
retryDelay: retryDelay,
cacheTime: cacheTime,
isDataEqual: isDataEqual,
queryFn: queryFn,
queryKey: queryKey,
queryHash: queryHash,
queryKeyHashFn: queryKeyHashFn,
initialData: initialData,
initialDataUpdatedAt: initialDataUpdatedAt,
behavior: behavior,
structuralSharing: structuralSharing,
getPreviousPageParam: getPreviousPageParam,
getNextPageParam: getNextPageParam,
defaulted: defaulted,
);
FetchQueryOptions.fromJson(Map<String, dynamic> json)
: staleTime = json["staleTime"],
super.fromJson(json);
@override
Map<String, dynamic> toJson() {
return {
...super.toJson(),
"staleTime": staleTime,
};
}
}
@@ -1,95 +0,0 @@
// TYPES
import 'dart:async';
typedef NotifyCallback = void Function();
typedef NotifyFunction = void Function(void Function() callback);
typedef BatchNotifyFunction = void Function(void Function() callback);
class NotifyManager {
List<NotifyCallback> _queue;
int _transactions;
late NotifyFunction _notifyFn;
late BatchNotifyFunction _batchNotifyFn;
NotifyManager()
: _queue = [],
_transactions = 0 {
_notifyFn = (void Function() callback) {
callback();
};
_batchNotifyFn = (void Function() callback) {
callback();
};
}
T batch<T>(T Function() callback) {
final T result;
_transactions++;
try {
result = callback();
} finally {
_transactions--;
if (_transactions == 0) {
flush();
}
}
return result;
}
void schedule(NotifyCallback callback) {
if (_transactions > 0) {
_queue.add(callback);
} else {
scheduleMicrotask(() {
_notifyFn(callback);
});
}
}
/// All calls to the wrapped function will be batched.
T batchCalls<T extends void Function(List? args)>(T callback) {
void fn(List? args) {
schedule(() {
callback(args);
});
}
;
return fn as T;
}
void flush() {
var queue = _queue;
_queue = [];
if (queue.isNotEmpty) {
scheduleMicrotask(() {
_batchNotifyFn(() {
queue.forEach((fn) {
_notifyFn(fn);
});
});
});
}
}
///Use this method to set a custom notify function.
void setNotifyFunction(NotifyFunction fn) {
_notifyFn = fn;
}
/// Use this method to set a custom function to batch notifications
/// together into a single tick.
/// By default React Query will use the batch function provided by
/// ReactDOM or React Native.
void setBatchNotifyFunction(BatchNotifyFunction fn) {
_batchNotifyFn = fn;
}
}
// SINGLETON
NotifyManager notifyManager = new NotifyManager();
@@ -1,72 +0,0 @@
import 'package:fl_query/src/core/subscribable.dart';
import 'package:internet_connection_checker/internet_connection_checker.dart';
typedef SetupFn = void Function()? Function(
void Function([bool? online]) setOnline);
class OnlineManager extends Subscribable {
bool? _online;
void Function()? _cleanup;
SetupFn? _setup;
OnlineManager([InternetConnectionChecker? connectionChecker]) {
connectionChecker ??= InternetConnectionChecker();
_setup = (listener) {
var subscription =
connectionChecker!.onStatusChange.listen((status) => listener());
return () {
subscription.cancel();
};
};
}
@override
void onSubscribe() {
if (_cleanup == null) {
setEventListener(_setup!);
}
}
@override
void onUnsubscribe() {
if (!hasListeners()) {
_cleanup?.call();
_cleanup = null;
}
}
void setEventListener(SetupFn setup) {
_setup = setup;
_cleanup?.call();
_cleanup = setup(([bool? online]) {
if (online != null) {
setOnline(online);
} else {
onOnline();
}
});
}
void setOnline(bool? online) {
_online = online;
if (online != null && online) {
onOnline();
}
}
void onOnline() {
listeners.forEach((listener) {
listener();
});
}
Future<bool> isOnline() {
if (_online != null) {
return Future.value(_online!);
}
return InternetConnectionChecker().hasConnection;
}
}
OnlineManager onlineManager = OnlineManager();
-666
View File
@@ -1,666 +0,0 @@
import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:math';
import 'package:fl_query/src/core/models.dart';
import 'package:fl_query/src/core/notify_manager.dart';
import 'package:fl_query/src/core/query_cache.dart';
import 'package:fl_query/src/core/query_key.dart';
import 'package:fl_query/src/core/query_observer.dart';
import 'package:fl_query/src/core/retryer.dart';
import 'package:fl_query/src/core/utils.dart';
import 'package:meta/meta.dart';
import 'package:collection/collection.dart';
class FetchOptions {
bool? cancelRefetch;
dynamic meta;
FetchOptions({this.cancelRefetch, this.meta});
}
class FetchContext<TQueryFnData extends Map<String, dynamic>, TError,
TData extends Map<String, dynamic>> {
FutureOr<TQueryFnData> Function() fetchFn;
FetchOptions? fetchOptions;
QueryOptions<TQueryFnData, TError, TData> options;
QueryKey queryKey;
QueryState<TData, TError> state;
QueryMeta? meta;
FetchContext({
required this.fetchFn,
required this.options,
required this.queryKey,
required this.state,
this.meta,
this.fetchOptions,
});
}
class QueryBehavior<TQueryFnData extends Map<String, dynamic>, TError,
TData extends Map<String, dynamic>> {
void Function(FetchContext<TQueryFnData, TError, TData> context) onFetch;
QueryBehavior({required this.onFetch});
}
class QueryState<TData extends Map<String, dynamic>, TError> {
TData? data;
TError? error;
QueryStatus status;
DateTime? dataUpdatedAt;
int dataUpdateCount;
DateTime? errorUpdatedAt;
int errorUpdateCount;
int fetchFailureCount;
dynamic fetchMeta;
bool isFetching;
bool isInvalidated;
bool isPaused;
QueryState({
required this.status,
required this.dataUpdatedAt,
required this.dataUpdateCount,
required this.errorUpdatedAt,
required this.errorUpdateCount,
required this.fetchFailureCount,
required this.fetchMeta,
required this.isFetching,
required this.isInvalidated,
required this.isPaused,
this.data,
this.error,
});
QueryState.fromJson(Map<String, dynamic> json)
: data = json["data"],
error = json["error"],
status = json["status"],
dataUpdatedAt = json["dataUpdatedAt"],
dataUpdateCount = json["dataUpdateCount"],
errorUpdatedAt = json["errorUpdatedAt"],
errorUpdateCount = json["errorUpdateCount"],
fetchFailureCount = json["fetchFailureCount"],
fetchMeta = json["fetchMeta"],
isFetching = json["isFetching"],
isInvalidated = json["isInvalidated"],
isPaused = json["isPaused"];
Map<String, dynamic> toJson() {
return {
"data": data,
"error": error,
"status": status,
"dataUpdatedAt": dataUpdatedAt,
"dataUpdateCount": dataUpdateCount,
"errorUpdatedAt": errorUpdatedAt,
"errorUpdateCount": errorUpdateCount,
"fetchFailureCount": fetchFailureCount,
"fetchMeta": fetchMeta,
"isFetching": isFetching,
"isInvalidated": isInvalidated,
"isPaused": isPaused,
};
}
}
enum ActionType {
failed,
fetch,
success,
error,
invalidate,
pause,
resume,
setState,
}
class SetStateOptions {
Object? meta;
SetStateOptions({this.meta});
Map<String, dynamic> toJson() {
return {"meta": meta};
}
}
class Action<TData extends Map<String, dynamic>, TError> {
ActionType type;
Object? meta;
TData? data;
DateTime? dataUpdatedAt;
TError? error;
QueryState<TData, TError>? state;
SetStateOptions? setStateOptions;
Action(
this.type, {
this.meta,
this.data,
this.dataUpdatedAt,
this.error,
this.state,
this.setStateOptions,
}) {
if (type == ActionType.error && error == null)
throw Exception(
"[Action.Action] property `error` can't be null when `type` = `$type`");
if (type == ActionType.setState && state == null)
throw Exception(
"[Action.Action] property `state` can't be null when `type` = `$type`");
}
Map<String, dynamic> toJson() {
return {
"type": type,
"meta": meta,
"data": data,
"dataUpdatedAt": dataUpdatedAt,
"error": error,
"state": state,
"setStateOptions": setStateOptions,
};
}
}
class Query<TQueryFnData extends Map<String, dynamic>, TError,
TData extends Map<String, dynamic>> {
QueryKey queryKey;
String queryHash;
late QueryOptions<TQueryFnData, TError, TData> options;
late QueryState<TData, TError> initialState;
QueryState<TData, TError>? revertState;
late QueryState<TData, TError> state;
Duration? cacheTime;
QueryMeta? meta;
QueryCache _cache;
// Future<TData>? _future;
Completer<TData>? _completer;
Timer? _gcTimeout;
Retryer<TData, TError>? _retryer;
List<QueryObserver> _observers;
QueryOptions<TQueryFnData, TError, TData>? _defaultOptions;
bool _abortSignalConsumed;
bool _hadObservers;
Query({
required this.queryKey,
required this.queryHash,
required QueryCache cache,
QueryOptions<TQueryFnData, TError, TData>? options,
QueryOptions<TQueryFnData, TError, TData>? defaultOptions,
QueryState<TData, TError>? state,
QueryMeta? meta,
}) : _abortSignalConsumed = false,
_hadObservers = false,
_defaultOptions = defaultOptions,
_observers = [],
_cache = cache {
_setOptions(options);
initialState = state ?? _getDefaultState(this.options);
this.state = initialState;
this.meta = meta;
_scheduleGc();
}
void _scheduleGc() {
this._clearGcTimeout();
if (this.cacheTime != null) {
_gcTimeout = Timer(cacheTime!, () {
this._optionalRemove();
});
}
}
void _clearGcTimeout() {
_gcTimeout?.cancel();
_gcTimeout = null;
}
void _optionalRemove() {
if (_observers.isEmpty) {
if (state.isFetching) {
if (_hadObservers) {
_scheduleGc();
}
} else {
_cache.remove(this);
}
}
}
void _setOptions(QueryOptions<TQueryFnData, TError, TData>? options) {
this.options = QueryOptions.fromJson({
...(_defaultOptions?.toJson() ?? {}),
...(options?.toJson() ?? {}),
});
meta = options?.meta;
/// Default to [5 minutes] if cache time isn't set
cacheTime = Duration(
milliseconds: max(
cacheTime?.inMilliseconds ?? 0,
this.options.cacheTime?.inMilliseconds ?? 5 * 60 * 1000,
));
}
QueryState<TData, TError> _getDefaultState(
QueryOptions<TQueryFnData, TError, TData> options) {
var data = options.initialData;
bool hasData = data != null;
DateTime? initialDataUpdatedAt =
hasData ? options.initialDataUpdatedAt : null;
return QueryState(
data: data,
dataUpdateCount: 0,
dataUpdatedAt: hasData ? initialDataUpdatedAt ?? DateTime.now() : null,
error: null,
errorUpdateCount: 0,
errorUpdatedAt: null,
fetchFailureCount: 0,
fetchMeta: null,
isFetching: false,
isInvalidated: false,
isPaused: false,
status: hasData ? QueryStatus.success : QueryStatus.idle,
);
}
TData setData(
DataUpdateFunction<TData?, TData> updater, {
DateTime? updatedAt,
}) {
try {
var prevData = this.state.data;
var data = updater(prevData);
// Use prev data if an isDataEqual function is defined and returns `true`
if (this.options.isDataEqual?.call(prevData, data) == true) {
data = prevData as TData;
} else if (this.options.structuralSharing != false) {
// Structurally share data between prev and new data if needed
final merged =
Map<String, dynamic>.from(replaceEqualDeep(prevData, data));
data = merged as TData;
}
// Set data and mark it as cached
_dispatch(Action(
ActionType.success,
data: data,
dataUpdatedAt: updatedAt,
));
return data;
} catch (e, stack) {
print("[Query.setData] $e");
print(stack);
rethrow;
}
}
void setState(
QueryState<TData, TError> state, [
SetStateOptions? setStateOptions,
]) {
_dispatch(Action(
ActionType.setState,
state: state,
setStateOptions: setStateOptions,
));
}
Future<void> cancel({bool? revert, bool? silent}) {
// var future = _future;
_retryer?.cancel(revert: revert, silent: silent);
if (_completer != null && !_completer!.isCompleted) {
_completer!.completeError("Cancelled Job", StackTrace.current);
return _completer!.future.then(noop).catchError(noop);
}
return Future.value();
}
void reset() {
destroy();
setState(initialState);
}
destroy() {
_clearGcTimeout();
cancel(silent: true);
}
bool isActive() {
return _observers.any((observer) => observer.options.enabled != false);
}
bool isFetching() {
return this.state.isFetching;
}
Future<TData> fetch([
QueryOptions<TQueryFnData, TError, TData>? options,
ObserverFetchOptions? fetchOptions,
]) {
if (this.state.isFetching) {
if (this.state.dataUpdatedAt != null &&
fetchOptions?.cancelRefetch == true) {
// Silently cancel current fetch if the user wants to cancel re-fetches
this.cancel(silent: true);
} else if (_completer != null) {
// make sure that retries that were potentially cancelled due to unmounts can continue
_retryer?.continueRetry();
// Return current promise if we are already fetching
return _completer!.future;
}
}
// Update config if passed, otherwise the config from the last execution is used
if (options != null) {
_setOptions(options);
}
// Use the options from the first observer with a query function if no function is found.
// This can happen when the query is hydrated or created with setQueryData.
if (this.options.queryFn == null) {
final observer =
_observers.firstWhereOrNull((x) => x.options.queryFn != null);
if (observer != null) {
_setOptions(QueryOptions<TQueryFnData, TError, TData>(
queryKey: observer.options.queryKey,
queryKeyHashFn: observer.options.queryKeyHashFn,
cacheTime: observer.options.cacheTime,
isDataEqual: observer.options.isDataEqual,
queryFn:
observer.options.queryFn as QueryFunction<TQueryFnData, dynamic>,
queryHash: observer.options.queryHash,
initialData: observer.options.initialData as TData?,
initialDataUpdatedAt: observer.options.initialDataUpdatedAt,
meta: observer.options.meta,
structuralSharing: observer.options.structuralSharing,
defaulted: observer.options.defaulted,
));
}
}
QueryFunctionContext queryFnContext = QueryFunctionContext(
queryKey: queryKey,
meta: meta,
);
/// !!LANGUAGE LIMITATION!! There's no equivalent of [AbortController]
/// the [get] can be implemented using Dart's getter but it'd be
/// useless since there's no equivalent of AbortController.
/// Have to find a better way to control ABORTION
// Object.defineProperty(queryFnContext, 'signal', {
// enumerable: true,
// get: () {
// if (abortController) {
// this.abortSignalConsumed = true
// return abortController.signal
// }
// return undefined
// },
// })
// Create fetch function
FutureOr<TQueryFnData> fetchFn() {
if (this.options.queryFn == null) {
return Future.error('Missing queryFn');
}
_abortSignalConsumed = false;
return options!.queryFn!.call(queryFnContext);
}
// Trigger behavior hook
FetchContext<TQueryFnData, TError, TData> context =
FetchContext<TQueryFnData, TError, TData>(
fetchOptions: fetchOptions,
options: this.options,
queryKey: queryKey,
state: this.state,
fetchFn: fetchFn,
meta: this.meta,
);
this.options.behavior?.onFetch(context);
// Store state in case the current fetch needs to be reverted
this.revertState = this.state;
// Set to fetching state if not already in it
if (!this.state.isFetching ||
this.state.fetchMeta != context.fetchOptions?.meta) {
_dispatch(Action(ActionType.fetch, meta: context.fetchOptions?.meta));
}
_retryer = Retryer(
fn: context.fetchFn as FutureOr<TData> Function(),
// abort: abortController?.abort?.bind(abortController),
onSuccess: (data) {
this.setData((_) => data);
// Notify cache callback
_cache.onData?.call(data, this);
if (_completer?.isCompleted == false) _completer?.complete(data);
// Remove query after fetching if cache time is 0
if (this.cacheTime == null || this.cacheTime == Duration.zero) {
_optionalRemove();
}
},
onError: (TError error) {
// Optimistically update state if needed
if (!(isCancelledError(error) && (error as dynamic)?.silent == true)) {
_dispatch(Action(ActionType.error, error: error));
}
if (!isCancelledError(error)) {
// Notify cache callback
_cache.onError?.call(error, this);
// Log error
// getLogger().error(error);
}
// Remove query after fetching if cache time is 0
if (this.cacheTime == null || this.cacheTime == Duration.zero) {
_optionalRemove();
}
if (_completer?.isCompleted == false)
_completer?.completeError(
error ?? "Retry Failed", StackTrace.current);
},
onFail: (failureCount, error) {
_dispatch(Action(ActionType.failed));
},
onPause: () {
_dispatch(Action(ActionType.pause));
},
onContinue: () {
_dispatch(Action(ActionType.resume));
},
retry: context.options.retry,
retryDelay: context.options.retryDelay,
);
this._completer = _retryer!.completer;
return this._completer!.future;
}
void _dispatch(Action<TData, TError> action) {
this.state = this.reducer(this.state, action);
notifyManager.batch(() {
_observers.forEach((observer) {
observer.onQueryUpdate(action);
});
_cache.notify(QueryCacheNotifyEvent(
QueryCacheNotifyEventType.queryUpdated,
this,
action: action,
));
});
}
void addObserver(QueryObserver observer) {
if (_observers.indexOf(observer) == -1) {
_observers.add(observer);
_hadObservers = true;
// Stop the query from being garbage collected
_clearGcTimeout();
_cache.notify(QueryCacheNotifyEvent(
QueryCacheNotifyEventType.observerAdded,
this,
observer: observer,
));
}
}
void removeObserver(QueryObserver observer) {
if (_observers.indexOf(observer) != -1) {
_observers = _observers.where((x) => x != observer).toList();
if (_observers.isEmpty) {
// If the transport layer does not support cancellation
// we'll let the query continue so the result can be cached
if (_retryer != null) {
if (_retryer?.isTransportCancelable == true || _abortSignalConsumed) {
_retryer?.cancel(revert: true);
} else {
_retryer?.cancelRetry();
}
}
if (cacheTime != null) {
_scheduleGc();
} else {
_cache.remove(this);
}
}
_cache.notify(QueryCacheNotifyEvent(
QueryCacheNotifyEventType.observerRemoved,
this,
observer: observer,
));
}
}
int getObserversCount() {
return _observers.length;
}
void invalidate() {
if (!this.state.isInvalidated) {
_dispatch(Action(ActionType.invalidate));
}
}
bool isStale() {
return (this.state.isInvalidated ||
this.state.dataUpdatedAt == null ||
_observers
.any((observer) => observer.getCurrentResult()?.isStale == true));
}
bool isStaleByTime(Duration? staleTime) {
return (this.state.isInvalidated ||
this.state.dataUpdatedAt == null ||
timeUntilStale(this.state.dataUpdatedAt!, staleTime) == Duration.zero);
}
void onOnline() {
var observer = _observers
.firstWhereOrNull((x) => x.shouldFetchCurrentQueryOnReconnect());
if (observer != null) {
observer.refetch();
}
// Continue fetch if currently paused
_retryer?.continueFn();
}
@protected
QueryState<TData, TError> reducer(
QueryState<TData, TError> state,
Action<TData, TError> action,
) {
switch (action.type) {
case ActionType.failed:
return QueryState.fromJson({
...state.toJson(),
"fetchFailureCount": state.fetchFailureCount + 1,
});
case ActionType.fetch:
return QueryState.fromJson({
...state.toJson(),
"fetchFailureCount": 0,
"fetchMeta": action.meta,
"isFetching": true,
"isPaused": false,
if (state.dataUpdatedAt == null)
...({
"error": null,
"status": QueryStatus.loading,
})
});
case ActionType.success:
return QueryState.fromJson({
...state.toJson(),
"data": action.data,
"dataUpdateCount": state.dataUpdateCount + 1,
"dataUpdatedAt": action.dataUpdatedAt ?? DateTime.now(),
"error": null,
"fetchFailureCount": 0,
"isFetching": false,
"isInvalidated": false,
"isPaused": false,
"status": QueryStatus.success,
});
case ActionType.error:
var error = action.error as dynamic;
if (isCancelledError(error) &&
error?.revert == true &&
revertState != null) {
return QueryState.fromJson(revertState!.toJson());
}
return QueryState.fromJson({
...state.toJson(),
"error": error as TError,
"errorUpdateCount": state.errorUpdateCount + 1,
"errorUpdatedAt": DateTime.now(),
"fetchFailureCount": state.fetchFailureCount + 1,
"isFetching": false,
"isPaused": false,
"status": QueryStatus.error,
});
case ActionType.invalidate:
return QueryState.fromJson({
...state.toJson(),
"isInvalidated": true,
});
case ActionType.pause:
return QueryState.fromJson({
...state.toJson(),
"isPaused": true,
});
case ActionType.resume:
return QueryState.fromJson({
...state.toJson(),
"isPaused": false,
});
case ActionType.setState:
return QueryState.fromJson({
...state.toJson(),
...(action.state?.toJson() ?? {}),
});
default:
return state;
}
}
}
@@ -1,190 +0,0 @@
import 'package:fl_query/src/core/models.dart';
import 'package:fl_query/src/core/notify_manager.dart';
import 'package:fl_query/src/core/query.dart';
import 'package:fl_query/src/core/query_client.dart';
import 'package:fl_query/src/core/query_key.dart';
import 'package:fl_query/src/core/subscribable.dart';
import 'package:fl_query/src/core/utils.dart';
import 'package:collection/collection.dart';
enum QueryCacheNotifyEventType {
observerResultsUpdated,
observerRemoved,
observerAdded,
queryUpdated,
queryRemoved,
queryAdded
}
class QueryCacheNotifyEvent {
Query query;
Object? observer;
Object? action;
QueryCacheNotifyEventType type;
QueryCacheNotifyEvent(
this.type,
this.query, {
this.observer,
this.action,
}) {
if ([
QueryCacheNotifyEventType.observerAdded,
QueryCacheNotifyEventType.observerRemoved
].contains(type) &&
observer == null)
throw Exception(
"[QueryCacheNotifyEvent.constructor] property `observer` can't be `null` for `QueryCacheNotifyEventType.observerAdded` & `QueryCacheNotifyEventType.observerRemoved`");
if (type == QueryCacheNotifyEventType.queryUpdated && action == null)
throw Exception(
"[QueryCacheNotifyEvent.constructor] property `action` can't be `null` for `QueryCacheNotifyEventType.queryUpdated`");
}
}
typedef QueryCacheListener = void Function(QueryCacheNotifyEvent? event);
typedef QueryCacheOnError = void Function(dynamic error, Query query);
typedef QueryCacheOnData = void Function(dynamic data, Query query);
typedef QueryHashMap = Map<String, Query>;
class QueryCache extends Subscribable<QueryCacheListener> {
List<Query> _queries;
QueryHashMap _queriesMap;
QueryCacheOnError? onError;
QueryCacheOnData? onData;
QueryCache({
this.onData,
this.onError,
}) : _queries = [],
_queriesMap = {},
super();
Query<TQueryFnData, TError, TData> build<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>>(
QueryClient client,
QueryOptions<TQueryFnData, TError, TData> options, [
QueryState<TData, TError>? state,
]) {
QueryKey queryKey = options.queryKey!;
String queryHash =
options.queryHash ?? hashQueryKeyByOptions(queryKey, options);
Query<TQueryFnData, TError, TData>? query =
get<TQueryFnData, TError, TData>(queryHash);
if (query == null) {
query = Query(
cache: this,
queryKey: queryKey,
queryHash: queryHash,
options: client.defaultQueryOptions(
QueryObserverOptions.fromJson(options.toJson()),
),
state: state,
defaultOptions: QueryOptions.fromJson(
client.getQueryDefaults(queryKey)?.toJson() ?? {},
),
meta: options.meta,
);
add(query);
}
return query;
}
QueryHashMap get queriesMap => _queriesMap;
List<Query> get queries => _queries;
void add(Query query) {
if (!_queriesMap.containsKey(query.queryHash)) {
_queriesMap[query.queryHash] = query;
_queries.add(query);
notify(
QueryCacheNotifyEvent(
QueryCacheNotifyEventType.queryAdded,
query,
),
);
}
}
void remove(Query query) {
Query? queryInMap = _queriesMap[query.queryHash];
if (queryInMap == null) return;
query.destroy();
_queries = _queries.where((x) => x != query).toList();
if (queryInMap == query) {
_queriesMap.remove(query.queryHash);
}
notify(QueryCacheNotifyEvent(
QueryCacheNotifyEventType.queryRemoved,
query,
));
}
void clear() {
notifyManager.batch(() {
for (var query in _queries) {
remove(query);
}
});
}
Query<TQueryFnData, TError, TData>? get<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>>(String queryHash) {
return _queriesMap[queryHash] as Query<TQueryFnData, TError, TData>?;
}
List<Query> getAll() {
return _queries;
}
Query<TQueryFnData, TError, TData>? find<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>>(QueryKey queryKey,
[QueryFilters? queryFilters]) {
queryFilters ??= QueryFilters();
queryFilters.exact ??= true;
return _queries.firstWhereOrNull((query) => matchQuery(
queryFilters!,
query,
queryKey,
)) as Query<TQueryFnData, TError, TData>?;
}
List<Query> findAll([QueryKey? queryKeys, QueryFilters? filters]) {
return filters == null && queryKeys == null
? _queries
: _queries
.where(
(query) => matchQuery(
filters ?? QueryFilters(),
query,
queryKeys,
),
)
.toList();
}
void notify(QueryCacheNotifyEvent event) {
notifyManager.batch(() {
for (final listener in listeners) {
listener(event);
}
});
}
/// Dummy function just to keep the API similar to react-query
void onFocus() {}
void onOnline() {
notifyManager.batch(() {
_queries.forEach((query) {
query.onOnline();
});
});
}
}
@@ -1,390 +0,0 @@
import 'package:fl_query/src/core/models.dart';
import 'package:fl_query/src/core/notify_manager.dart';
import 'package:fl_query/src/core/online_manager.dart';
import 'package:fl_query/src/core/query.dart';
import 'package:fl_query/src/core/query_cache.dart';
import 'package:fl_query/src/core/query_key.dart';
import 'package:fl_query/src/core/query_observer.dart';
import 'package:fl_query/src/core/utils.dart';
import 'package:collection/collection.dart';
class QueryDefaults {
QueryKey queryKey;
QueryOptions defaultOptions;
QueryDefaults({
required this.queryKey,
required this.defaultOptions,
});
}
class MutationDefaults {
// QueryKey queryKey;
// QueryOptions defaultOptions;
// MutationDefaults({
// required this.queryKey,
// required this.defaultOptions,
// });
}
class QueryData<TData extends Map<String, dynamic>> {
QueryKey queryKey;
TData data;
QueryData({
required this.queryKey,
required this.data,
});
}
class QueryClient {
QueryCache _queryCache;
// QueryCache _mutationCache;
DefaultOptions _defaultOptions;
List<QueryDefaults> _queryDefaults;
// List<MutationDefaults> _mutationDefaults;
void Function()? _unsubscribeFocus;
void Function()? _unsubscribeOnline;
// MutationKey _mutationKey;
// MutationOptions<any, any, any, any> _mutationDefaultOptions;
QueryClient({
QueryCache? queryCache,
QueryCache? mutationCache,
DefaultOptions? defaultOptions,
}) : _queryCache = queryCache ?? QueryCache(),
_defaultOptions = defaultOptions ?? DefaultOptions(),
_queryDefaults = [];
/* _mutationDefaults = [], */
/* _mutationCache = mutationCache ?? QueryCache() */
void mount() {
// this.unsubscribeFocus = focusManager.subscribe(() => {
// if (focusManager.isFocused() && onlineManager.isOnline()) {
// this.mutationCache.onFocus()
// this.queryCache.onFocus()
// }
// })
_unsubscribeOnline = onlineManager.subscribe(() async {
if (/* focusManager.isFocused() && */ await onlineManager.isOnline()) {
// _mutationCache.onOnline();
_queryCache.onOnline();
}
});
}
void unmount() {
_unsubscribeFocus?.call();
_unsubscribeOnline?.call();
}
int isFetching({QueryKey? queryKey, QueryFilters? filters}) {
filters?.fetching = true;
return _queryCache.findAll(null, filters).length;
}
// int isMutating([MutationFilters? filters]) {
// return _mutationCache.findAll({ ...filters, fetching: true }).length
// }
TData? getQueryData<TData extends Map<String, dynamic>>(
QueryKey queryKey, [
QueryFilters? filters,
]) {
return _queryCache
.find<TData, dynamic, Map<String, dynamic>>(
queryKey, filters ?? QueryFilters())
?.state
.data as TData?;
}
List<QueryData<TData>> getQueriesData<TData extends Map<String, dynamic>>({
QueryKey? queryKeys,
QueryFilters? filters,
}) {
return getQueryCache().findAll(queryKeys, filters).map((query) {
return QueryData<TData>(
data: query.state.data as TData,
queryKey: query.queryKey,
);
}).toList();
}
TData setQueryData<TData extends Map<String, dynamic>>(
QueryKey queryKey,
DataUpdateFunction<TData?, TData> updater, [
DateTime? updatedAt,
]) {
final QueryOptions<Map<String, dynamic>, dynamic, TData> defaultedOptions =
QueryOptions<Map<String, dynamic>, dynamic, TData>.fromJson(
defaultQueryOptions<Map<String, dynamic>, dynamic, TData,
Map<String, dynamic>>(
QueryObserverOptions<Map<String, dynamic>, dynamic, TData,
Map<String, dynamic>>(queryKey: queryKey))
.toJson());
return _queryCache
.build<Map<String, dynamic>, dynamic, TData>(this, defaultedOptions)
.setData(
updater,
updatedAt: updatedAt,
);
}
List<QueryData> setQueriesData<TData extends Map<String, dynamic>>({
required DataUpdateFunction<TData?, TData> updater,
QueryKey? queryKeys,
QueryFilters? filters,
DateTime? updatedAt,
}) {
if (queryKeys == null && filters == null)
throw Exception(
"[QueryClient.setQueriesData] both `queryKey` & `filters` can't be null at the same time");
return notifyManager
.batch(() => getQueryCache().findAll(queryKeys, filters).map(
(query) => QueryData(
queryKey: query.queryKey,
data: setQueryData<TData>(
query.queryKey,
updater,
updatedAt,
),
),
))
.toList();
}
QueryState<TData, TError>?
getQueryState<TData extends Map<String, dynamic>, TError>(
QueryKey queryKey, [
QueryFilters? filters,
]) {
return _queryCache
.find<TData, TError, Map<String, dynamic>>(
queryKey,
filters ?? QueryFilters(),
)
?.state as QueryState<TData, TError>?;
}
void removeQueries({QueryKey? queryKeys, QueryFilters? filters}) {
notifyManager.batch(
() => {
_queryCache.findAll(queryKeys, filters).forEach((query) {
_queryCache.remove(query);
})
},
);
}
Future<void> resetQueries<TPageData>({
QueryKey? queryKeys,
RefetchableQueryFilters<TPageData>? filters,
bool? throwOnError,
}) {
filters?.active = true;
var refetchFilters = RefetchableQueryFilters<TPageData>.fromJson({
...(filters?.toJson() ?? {}),
"active": true,
});
return notifyManager.batch(() {
_queryCache.findAll(queryKeys, filters).forEach((query) {
query.reset();
});
return refetchQueries(
filters: refetchFilters,
options: RefetchOptions(throwOnError: throwOnError),
);
});
}
Future<void> cancelQueries({
QueryKey? queryKeys,
QueryFilters? filters,
bool? revert = true,
bool? silent,
}) {
var futures = notifyManager.batch(() =>
_queryCache.findAll(queryKeys, filters).map((query) => query.cancel(
revert: revert,
silent: silent,
)));
return Future.wait(futures).then(noop).catchError(noop);
}
Future<void> invalidateQueries<TPageData>({
QueryKey? queryKeys,
InvalidateQueryFilters<TPageData>? filters,
RefetchOptions? options,
}) {
var refetchFilters = RefetchableQueryFilters<TPageData>.fromJson({
...(filters?.toJson() ?? {}),
// if filters.refetchActive is not provided and filters.active is explicitly false,
// e.g. invalidateQueries({ active: false }), we don't want to refetch active queries
"active": filters?.refetchActive ?? filters?.active ?? true,
"inactive": filters?.refetchInactive ?? false,
});
return notifyManager.batch(() {
_queryCache.findAll(queryKeys, filters).forEach((query) {
query.invalidate();
});
return this.refetchQueries(
filters: refetchFilters,
options: options,
);
});
}
Future<void> refetchQueries<TPageData>({
QueryKey? queryKeys,
RefetchableQueryFilters<TPageData>? filters,
RefetchOptions? options,
}) {
var futures = notifyManager.batch(
() => _queryCache.findAll(queryKeys, filters).map(
(query) => query.fetch(
null,
ObserverFetchOptions(
cancelRefetch: options?.cancelRefetch,
throwOnError: options?.throwOnError,
meta: {"refetchPage": filters?.refetchPage},
),
),
),
);
var future = Future.wait(futures).then(noop);
if (options?.throwOnError == false) {
future = future.catchError(noop);
}
return future;
}
Future<TData> fetchQuery<TQueryFnData extends Map<String, dynamic>, TError,
TData extends Map<String, dynamic>>({
QueryKey? queryKey,
QueryFunction<TQueryFnData, dynamic>? queryFn,
FetchQueryOptions<TQueryFnData, TError, TData>? options,
}) {
final defaultedOptions = this.defaultQueryOptions(
QueryObserverOptions<Map<String, dynamic>, dynamic, Map<String, dynamic>,
TData>(
queryFn: queryFn,
queryKey: queryKey,
staleTime: options?.staleTime,
cacheTime: options?.cacheTime,
defaulted: options?.defaulted,
initialData: options?.initialData,
initialDataUpdatedAt: options?.initialDataUpdatedAt,
isDataEqual: options?.isDataEqual,
meta: options?.meta,
queryHash: options?.queryHash,
queryKeyHashFn: options?.queryKeyHashFn,
structuralSharing: options?.structuralSharing,
),
);
// returning 0 indicates turing off retry
defaultedOptions.retry ??= (_, __) => 0;
final query = _queryCache.build<Map<String, dynamic>, dynamic, TData>(
this, defaultedOptions);
return query.isStaleByTime(defaultedOptions.staleTime)
? query.fetch(defaultedOptions)
: Future.value(query.state.data as TData);
}
Future<void> prefetchQuery<TQueryFnData extends Map<String, dynamic>, TError,
TData extends Map<String, dynamic>>({
QueryKey? queryKey,
QueryFunction<TQueryFnData, dynamic>? queryFn,
FetchQueryOptions<TQueryFnData, TError, TData>? options,
}) {
return fetchQuery<TQueryFnData, dynamic, TData>(
queryKey: queryKey,
queryFn: queryFn,
options: options,
).then(noop).catchError(noop);
}
QueryObserverOptions<TQueryFnData, TError, TData,
TQueryData> defaultQueryOptions<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>,
TQueryData extends Map<String, dynamic>>(
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>? options) {
if (options?.defaulted == true) return options!;
final defaultedOptions =
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>.fromJson({
...(_defaultOptions.queries?.toJson() ?? {}),
...(getQueryDefaults(options?.queryKey)?.toJson() ?? {}),
...(options?.toJson() ?? {}),
"defaulted": true,
});
if (defaultedOptions.queryHash == null &&
defaultedOptions.queryKey != null) {
defaultedOptions.queryHash = hashQueryKeyByOptions(
defaultedOptions.queryKey!,
defaultedOptions,
);
}
return defaultedOptions;
}
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>
defaultQueryObserverOptions<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>,
TQueryData extends Map<String, dynamic>>([
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>? options,
]) {
return this.defaultQueryOptions(options);
}
DefaultOptions getDefaultOptions() {
return _defaultOptions;
}
void setDefaultOptions(DefaultOptions options) {
_defaultOptions = options;
}
QueryObserverOptions? getQueryDefaults([QueryKey? queryKey]) {
return queryKey != null
? QueryObserverOptions.fromJson((_queryDefaults
.firstWhereOrNull(
(x) => queryKey.key == x.queryKey.key,
)
?.defaultOptions)
?.toJson() ??
{})
: null;
}
void setQueryDefaults(QueryKey queryKey, QueryObserverOptions options) {
var result = _queryDefaults.firstWhereOrNull(
(x) => queryKey.key == x.queryKey.key,
);
if (result != null) {
result.defaultOptions = options;
} else {
_queryDefaults
.add(QueryDefaults(queryKey: queryKey, defaultOptions: options));
}
}
// getMutationDefaults() {}
// setMutationDefaults() {}
// getMutationCache() {}
QueryCache getQueryCache() {
return _queryCache;
}
void clear() {
_queryCache.clear();
// _mutationCache.clear();
}
}
@@ -1,18 +0,0 @@
/// Used for defining a unique identifier for a specific query
/// that can be used to read/modify/delete the query from the
/// store
class QueryKey {
List<String> _key;
QueryKey(String key) : _key = [key];
QueryKey.fromList(List<String> key) : _key = key;
QueryKey.parse(String keyStr) : _key = keyStr.split(".");
String get key => _key.map((k) => k.replaceAll(".", "")).join(".");
List<String> get keyAsList => _key;
@override
String toString() {
return 'QueryKey("$key")';
}
}
@@ -1,740 +0,0 @@
/// `TQueryData`, `TQueryFnData`, `TData` should be [Map]s for shallow/deep equality checks
/// Or these can be data classes that have `toJson` method & `fromJson`
/// constructor. This also requires the data-class to be passed to the
/// [Query] constructor parameters e.g ([dataType])
import 'dart:async';
import 'package:fl_query/src/core/models.dart';
import 'package:fl_query/src/core/notify_manager.dart';
import 'package:fl_query/src/core/query.dart';
import 'package:fl_query/src/core/query_cache.dart';
import 'package:fl_query/src/core/query_client.dart';
import 'package:fl_query/src/core/retryer.dart';
import 'package:fl_query/src/core/subscribable.dart';
import 'package:fl_query/src/core/utils.dart';
import 'package:meta/meta.dart';
typedef QueryObserverListener<TData extends Map<String, dynamic>, TError> = void
Function(QueryObserverResult<TData, TError> result);
class NotifyOptions {
bool? cache;
bool? listeners;
bool? onError;
bool? onSuccess;
NotifyOptions({this.cache, this.listeners, this.onError, this.onSuccess});
/// [safe] default `true`- if it's true then there'll be no key
/// containing null value
Map<String, dynamic> toJson([bool safe = true]) {
final Map<String, dynamic> data = new Map<String, dynamic>();
if (safe) {
if (this.cache != null) data['cache'] = this.cache;
if (this.listeners != null) data['listeners'] = this.listeners;
if (this.onError != null) data['onError'] = this.onError;
if (this.onSuccess != null) data['onSuccess'] = this.onSuccess;
} else {
data['cache'] = this.cache;
data['listeners'] = this.listeners;
data['onError'] = this.onError;
data['onSuccess'] = this.onSuccess;
}
return data;
}
NotifyOptions.fromJson(Map<String, dynamic> json) {
cache = json['cache'];
listeners = json['listeners'];
onError = json['onError'];
onSuccess = json['onSuccess'];
}
}
class ObserverFetchOptions extends FetchOptions {
bool? throwOnError;
ObserverFetchOptions({
this.throwOnError,
bool? cancelRefetch,
dynamic meta,
}) : super(cancelRefetch: cancelRefetch, meta: meta);
}
class SelectQuery<TQueryData extends Map<String, dynamic>,
TData extends Map<String, dynamic>> {
TData Function(TQueryData data) fn;
TData result;
SelectQuery(this.fn, this.result);
}
class QueryObserver<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>,
TQueryData extends Map<String, dynamic>>
extends Subscribable<QueryObserverListener> {
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options;
QueryClient _client;
Query<TQueryFnData, TError, TQueryData>? _currentQuery;
late QueryState<TQueryData, TError> _currentQueryInitialState;
QueryObserverResult<TData, TError>? _currentResult;
/// List of tracked keys/properties of [QueryObserverResult]
late List<String> _trackedProps;
QueryState<TQueryData, TError>? _currentResultState;
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>?
_currentResultOptions;
QueryObserverResult<TData, TError>? _previousQueryResult;
Exception? _previousSelectError;
SelectQuery<TQueryData, TData>? _previousSelect;
Timer? _staleTimeout;
Timer? _refetchInterval;
Duration? _currentRefetchInterval;
@protected
Timer? get refetchInterval => _refetchInterval;
QueryObserver(
this._client,
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>? _options,
) : _trackedProps = [],
_previousSelectError = null,
options = _options ?? QueryObserverOptions(),
super() {
this.setOptions(options);
}
bool shouldFetchCurrentQueryOnReconnect() {
return shouldFetchOnReconnect(_currentQuery!, this.options);
}
@override
void onSubscribe() {
if (listeners.length == 1) {
_currentQuery?.addObserver(this);
if (_currentQuery != null &&
shouldFetchOnMount(_currentQuery!, options)) {
_executeFetch();
}
_updateTimers();
}
}
@override
void onUnsubscribe() {
if (listeners.isEmpty) {
this.destroy();
}
}
void destroy() {
listeners = [];
_clearTimers();
_currentQuery?.removeObserver(this);
}
void setOptions(
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>? options, [
NotifyOptions? notifyOptions,
]) {
final prevOptions = this.options;
final prevQuery = _currentQuery;
this.options = this._client.defaultQueryObserverOptions(options);
this.options.queryKey ??= prevOptions.queryKey;
_updateQuery();
bool mounted = hasListeners();
if (mounted &&
_currentQuery != null &&
prevQuery != null &&
shouldFetchOptionally(
_currentQuery!, prevQuery, this.options, prevOptions)) {
_executeFetch();
}
;
this.updateResult(notifyOptions);
if (mounted &&
(_currentQuery != prevQuery ||
this.options.enabled != prevOptions.enabled ||
this.options.staleTime != prevOptions.staleTime)) {
_updateStaleTimeout();
}
final nextRefetchInterval = _computeRefetchInterval();
// Update refetch interval if needed
if (mounted &&
(_currentQuery != prevQuery ||
this.options.enabled != prevOptions.enabled ||
nextRefetchInterval != _currentRefetchInterval)) {
_updateRefetchInterval(nextRefetchInterval);
}
}
QueryObserverResult<TData, TError> getOptimisticResult(
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options,
) {
final defaultedOptions = _client.defaultQueryObserverOptions(options);
final query = _client.getQueryCache().build(_client, defaultedOptions);
return createResult(query, defaultedOptions);
}
QueryObserverResult<TData, TError>? getCurrentResult() {
return _currentResult;
}
/// There's nothing similar to JS [defineProperty] in dart native
/// objects thus modifying the underlying property `get` method is
/// impossible so [trackProp] can't be implemented at the moment
/// At least not following this procedure
QueryObserverResult<TData, TError> trackResult(
QueryObserverResult<TData, TError> result,
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData>
defaultedOptions,
) {
// final Map<String, dynamic> trackedResult = <String, dynamic>{};
// const trackProp = (key: keyof QueryObserverResult) => {
// if (!this.trackedProps.includes(key)) {
// this.trackedProps.push(key)
// }
// }
// Object.keys(result).forEach(key => {
// Object.defineProperty(trackedResult, key, {
// configurable: false,
// enumerable: true,
// get: () => {
// trackProp(key as keyof QueryObserverResult)
// return result[key as keyof QueryObserverResult]
// },
// })
// })
// if (defaultedOptions.useErrorBoundary || defaultedOptions.suspense) {
// trackProp('error')
// }
// return trackedResult
throw UnimplementedError("COULD NOT IMPLEMENT DUE TO LANGUAGE LIMITATIONS");
}
Future<QueryObserverResult<TData, TError>> getNextResult([
bool? throwOnError,
]) {
final completer = Completer<QueryObserverResult<TData, TError>>();
var unsubscribe;
unsubscribe = subscribe((result) {
if (!result.isFetching) {
unsubscribe?.call();
if (result.isError && throwOnError == true) {
if (!completer.isCompleted) completer.completeError(result.error);
} else {
if (!completer.isCompleted)
completer.complete(
result as QueryObserverResult<TData, TError>,
);
}
}
});
return completer.future;
}
Query<TQueryFnData, TError, TQueryData> getCurrentQuery() {
return _currentQuery!;
}
Future<QueryObserverResult<TData, TError>> fetchOptimistic(
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options) {
final defaultedOptions = _client.defaultQueryObserverOptions(options);
final query = _client.getQueryCache().build(_client, defaultedOptions);
return query.fetch().then((val) {
return createResult(query, defaultedOptions);
});
}
@protected
Future<QueryObserverResult<TData, TError>?> fetch(
ObserverFetchOptions fetchOptions,
) {
return _executeFetch(fetchOptions).then((val) {
updateResult();
return _currentResult;
});
}
Future<TQueryData?> _executeFetch([ObserverFetchOptions? fetchOptions]) {
// Make sure we reference the latest query as the current one might have been removed
_updateQuery();
// Fetch
Future<TQueryData?> future = _currentQuery!.fetch(
this.options,
fetchOptions,
);
if (fetchOptions?.throwOnError != null) {
future = future.catchError((e) => e);
}
return future;
}
bool _shouldNotifyListeners(QueryObserverResult<TData, TError> result,
[QueryObserverResult<TData, TError>? prevResult]) {
if (prevResult == null) return true;
if (options.notifyOnChangeProps == false &&
options.notifyOnChangePropsExclusions == null) {
return true;
}
if (options.notifyOnChangeProps == 'tracked' && _trackedProps.isEmpty) {
return true;
}
List<String>? includedProps = options.notifyOnChangeProps == 'tracked'
? _trackedProps
: options.notifyOnChangeProps;
Map<String, dynamic> resultMap = result.toJson();
Map<String, dynamic> prevResultMap = prevResult.toJson();
return resultMap.keys.any((key) {
final changed = resultMap[key] != prevResultMap[key];
bool? isIncluded = includedProps?.any((x) => x == key);
bool isExcluded =
options.notifyOnChangePropsExclusions?.any((x) => x == key) ?? false;
return changed &&
!isExcluded &&
(includedProps == null || isIncluded == true);
});
}
void updateResult([NotifyOptions? notifyOptions]) {
final QueryObserverResult<TData, TError>? prevResult = _currentResult;
if (_currentQuery != null)
_currentResult = this.createResult(_currentQuery!, this.options);
_currentResultState = _currentQuery?.state;
_currentResultOptions = this.options;
final isSameMap =
shallowEqualMap(_currentResult?.toJson(), prevResult?.toJson());
// Only notify if something has changed
if (isSameMap) {
return;
}
NotifyOptions defaultNotifyOptions = NotifyOptions(cache: true);
if (notifyOptions?.listeners != false &&
_currentResult != null &&
_shouldNotifyListeners(_currentResult!, prevResult)) {
defaultNotifyOptions.listeners = true;
}
final mergedNotifyOptions = {
...defaultNotifyOptions.toJson(),
...(notifyOptions?.toJson() ?? {}),
};
_notify(NotifyOptions.fromJson(mergedNotifyOptions));
}
void _updateQuery() {
final query =
this._client.getQueryCache().build(this._client, this.options);
if (query == _currentQuery) return;
final prevQuery = _currentQuery;
_currentQuery = query;
_currentQueryInitialState = query.state;
_previousQueryResult = _currentResult;
if (hasListeners()) {
prevQuery?.removeObserver(this);
query.addObserver(this);
}
}
void onQueryUpdate(Action<TData, TError> action) {
final NotifyOptions notifyOptions = NotifyOptions();
if (action.type == 'success') {
notifyOptions.onSuccess = true;
} else if (action.type == 'error' && !isCancelledError(action.error)) {
notifyOptions.onError = true;
}
updateResult(notifyOptions);
if (this.hasListeners()) {
_updateTimers();
}
}
QueryObserverResult<TData, TError> createResult(
Query<TQueryFnData, TError, TQueryData> query,
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options,
) {
final prevQuery = _currentQuery;
final prevOptions = this.options;
final prevResult = _currentResult;
final prevResultState = _currentResultState;
final prevResultOptions = _currentResultOptions;
final bool queryChange = query != prevQuery;
final queryInitialState =
queryChange ? query.state : _currentQueryInitialState;
final prevQueryResult = queryChange ? _currentResult : _previousQueryResult;
final state = query.state;
DateTime? dataUpdatedAt = state.dataUpdatedAt;
TError? error = state.error;
DateTime? errorUpdatedAt = state.errorUpdatedAt;
bool isFetching = state.isFetching;
QueryStatus status = state.status;
bool isPreviousData = false;
bool isPlaceholderData = false;
TData? data;
// Optimistically set result in fetching state if needed
if (options.optimisticResults == true) {
final bool mounted = hasListeners();
final bool fetchOnMount = !mounted && shouldFetchOnMount(query, options);
bool fetchOptionally = mounted &&
prevQuery != null &&
shouldFetchOptionally(query, prevQuery, options, prevOptions);
if (fetchOnMount || fetchOptionally) {
isFetching = true;
if (dataUpdatedAt == null) {
status = QueryStatus.loading;
}
}
}
// Keep previous data if needed
if (prevQueryResult != null &&
options.keepPreviousData == true &&
state.dataUpdateCount == 0 &&
prevQueryResult.isSuccess == true &&
status != QueryStatus.error) {
data = prevQueryResult.data;
dataUpdatedAt = prevQueryResult.dataUpdatedAt;
status = prevQueryResult.status;
isPreviousData = true;
}
// Select data if needed
else if (options.select != null && state.data != null) {
print("prevResult != null ${prevResult != null}");
print(
"state.data == prevResultState?.data | ${state.data} == ${prevResultState?.data} | ${shallowEqualMap(state.data, prevResultState?.data)}");
print(
"options.select == _previousSelect?.fn ${options.select == _previousSelect?.fn}");
print("_previousSelectError == null ${_previousSelectError == null}");
if (prevResult != null &&
shallowEqualMap(state.data, prevResultState?.data) &&
options.select == _previousSelect?.fn &&
_previousSelectError == null) {
data = _previousSelect?.result;
} else {
try {
data = options.select?.call(state.data);
if (options.structuralSharing != false) {
data = Map<String, dynamic>.from(
replaceEqualDeep(prevResult?.data, data)) as TData;
}
if (options.select != null && data != null) {
_previousSelect = SelectQuery<TQueryData, TData>(
options.select!,
data,
);
}
_previousSelectError = null;
} catch (selectError) {
// getLogger().error(selectError);
error = selectError as TError;
_previousSelectError = selectError as Exception;
errorUpdatedAt = DateTime.now();
status = QueryStatus.error;
}
}
}
// Use query data
else {
data = state.data as TData?;
}
if (options.placeholderData != null &&
data == null &&
(status == QueryStatus.loading || status == QueryStatus.idle)) {
var placeholderData;
if (prevResult?.isPlaceholderData == true &&
options.placeholderData == prevResultOptions?.placeholderData) {
placeholderData = prevResult?.data;
} else {
placeholderData = options.placeholderData;
if (options.select != null && placeholderData != null) {
try {
placeholderData = options.select?.call(placeholderData);
if (options.structuralSharing != false) {
placeholderData =
replaceEqualDeep(prevResult?.data, placeholderData);
}
_previousSelectError = null;
} catch (selectError) {
// getLogger().error(selectError);
error = selectError as TError;
_previousSelectError = selectError as Exception;
errorUpdatedAt = DateTime.now();
status = QueryStatus.error;
}
}
}
if (placeholderData != null) {
status = QueryStatus.success;
data = placeholderData as TData;
isPlaceholderData = true;
}
}
final QueryObserverResult<TData, TError> result =
QueryObserverResult<TData, TError>(
status: status,
dataUpdatedAt: dataUpdatedAt,
isLoading: status == QueryStatus.loading,
isSuccess: status == QueryStatus.success,
isError: status == QueryStatus.error,
isIdle: status == QueryStatus.idle,
data: data,
error: error,
failureCount: state.fetchFailureCount,
isFetched: state.dataUpdateCount > 0 || state.errorUpdateCount > 0,
isFetchedAfterMount:
state.dataUpdateCount > queryInitialState.dataUpdateCount ||
state.errorUpdateCount > queryInitialState.errorUpdateCount,
isFetching: isFetching,
isRefetching: isFetching && status != QueryStatus.loading,
isLoadingError:
status == QueryStatus.error && state.dataUpdatedAt == null,
isPlaceholderData: isPlaceholderData,
isPreviousData: isPreviousData,
isRefetchError: status == 'error' && state.dataUpdatedAt != 0,
isStale: isStale(query, options),
refetch: this.refetch,
remove: this.remove,
);
return result;
}
void _notify(NotifyOptions notifyOptions) {
notifyManager.batch(() {
// First trigger the configuration callbacks
if (notifyOptions.onSuccess == true && _currentResult != null) {
this.options.onSuccess?.call(_currentResult!.data!);
this.options.onSettled?.call(_currentResult!.data!);
} else if (notifyOptions.onError == true && _currentResult != null) {
this.options.onError?.call(_currentResult!.error!);
this.options.onSettled?.call(null, _currentResult!.error!);
}
// Then trigger the listeners
if (notifyOptions.listeners == true && _currentResult != null) {
this.listeners.forEach((listener) {
listener(_currentResult!);
});
}
// Then the cache listeners
if (notifyOptions.cache == true && _currentQuery != null) {
_client.getQueryCache().notify(
QueryCacheNotifyEvent(
QueryCacheNotifyEventType.observerResultsUpdated,
_currentQuery as Query,
),
);
}
});
}
Duration? _computeRefetchInterval() {
return this.options.refetchInterval != null && _currentQuery != null
? this.options.refetchInterval!(_currentResult?.data, _currentQuery!)
: null;
}
void _updateTimers() {
_updateStaleTimeout();
_updateRefetchInterval(_computeRefetchInterval());
}
void _updateStaleTimeout() {
_clearStaleTimeout();
if (_currentResult?.isStale == true ||
options.staleTime == null ||
_currentResult?.dataUpdatedAt == null) return;
// The timeout is sometimes triggered 1 ms before the stale time
// expiration. To mitigate this issue we always add 1 ms to the
// timeout.
Duration time = Duration(
milliseconds:
timeUntilStale(_currentResult!.dataUpdatedAt!, this.options.staleTime)
.inMilliseconds +
1,
);
_staleTimeout = Timer(time, () {
if (!_currentResult!.isStale) {
this.updateResult();
}
});
}
_updateRefetchInterval(Duration? nextInterval) {
_clearRefetchInterval();
_currentRefetchInterval = nextInterval;
if (this.options.enabled == false ||
_currentRefetchInterval == null ||
_currentRefetchInterval == Duration.zero) return;
_refetchInterval = Timer.periodic(_currentRefetchInterval!, (t) {
if (this.options.refetchIntervalInBackground == true) {
_executeFetch();
}
});
}
void _clearTimers() {
_clearStaleTimeout();
_clearRefetchInterval();
}
void _clearStaleTimeout() {
_staleTimeout?.cancel();
_staleTimeout = null;
}
void _clearRefetchInterval() {
_refetchInterval?.cancel();
_refetchInterval = null;
}
void remove() {
_client.getQueryCache().remove(_currentQuery as Query);
_clearTimers();
_currentQuery?.removeObserver(this);
}
Future<QueryObserverResult<TData, TError>?> refetch<TPageData>({
RefetchableQueryFilters<TPageData>? filters,
RefetchOptions? options,
}) {
return fetch(
ObserverFetchOptions(
cancelRefetch: options?.cancelRefetch,
meta: filters?.toJson(),
throwOnError: options?.throwOnError,
),
);
}
}
bool shouldLoadOnMount<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>,
TQueryData extends Map<String, dynamic>>(
Query<TQueryFnData, TError, TQueryData> query,
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options,
) {
return (options.enabled != false &&
query.state.dataUpdatedAt == null &&
!(query.state.status == QueryStatus.error &&
options.retryOnMount == false));
}
bool shouldRefetchOnMount<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>,
TQueryData extends Map<String, dynamic>>(
Query<TQueryFnData, TError, TQueryData> query,
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options,
) {
return (options.enabled != false &&
query.state.dataUpdatedAt != null &&
(options.refetchOnMount == RefetchOnMount.always ||
(options.refetchOnMount != RefetchOnMount.off &&
isStale(query, options))));
}
bool shouldFetchOnMount<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>,
TQueryData extends Map<String, dynamic>>(
Query<TQueryFnData, TError, TQueryData> query,
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options,
) {
return (shouldLoadOnMount(query, options) ||
shouldRefetchOnMount(query, options));
}
bool shouldFetchOnReconnect<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>,
TQueryData extends Map<String, dynamic>>(
Query<TQueryFnData, TError, TQueryData> query,
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options,
) {
return (options.enabled != false &&
(options.refetchOnReconnect == RefetchOnReconnect.always ||
(options.refetchOnReconnect != RefetchOnReconnect.off &&
isStale<TQueryFnData, TError, TData, TQueryData>(
query, options))));
}
bool shouldFetchOptionally<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>,
TQueryData extends Map<String, dynamic>>(
Query<TQueryFnData, TError, TQueryData> query,
Query<TQueryFnData, TError, TQueryData> prevQuery,
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options,
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> prevOptions,
) {
return (options.enabled != false &&
(query != prevQuery || prevOptions.enabled == false) &&
(options.suspense != true || query.state.status != QueryStatus.error) &&
isStale(query, options));
}
bool isStale<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>,
TQueryData extends Map<String, dynamic>>(
Query<TQueryFnData, TError, TQueryData> query,
QueryObserverOptions<TQueryFnData, TError, TData, TQueryData> options,
) {
return query.isStaleByTime(options.staleTime);
}
-202
View File
@@ -1,202 +0,0 @@
import 'dart:async';
import 'dart:math' show pow, min;
import 'package:fl_query/src/core/online_manager.dart';
typedef ShouldRetryFunction<TError> = int Function(
int failureCount,
TError error,
);
typedef RetryDelayFunction<TError> = double Function(
int failureCount,
TError error,
);
double defaultRetryDelay(int failureCount) {
return min(pow(1000 * 2, failureCount), 30000).toDouble();
}
abstract class Cancelable {
void cancel();
}
bool isCancelable(value) {
return value is Cancelable;
}
class CancelledError {
bool? revert;
bool? silent;
CancelledError({this.revert, this.silent});
@override
String toString() {
return "CancelledError(revert: $revert, silent: $silent)";
}
}
bool isCancelledError(value) {
return value is CancelledError;
}
typedef OnError<TError> = void Function(TError error);
typedef OnData<TData extends Map<String, dynamic>> = void Function(TData data);
class Retryer<TData extends Map<String, dynamic>, TError> {
late void Function({bool? revert, bool? silent}) cancel;
late void Function() cancelRetry;
late void Function() continueRetry;
late void Function() continueFn;
// late Future<TData> future;
late Completer<TData> completer;
int failureCount;
bool isPaused;
bool isResolved;
bool isTransportCancelable;
// config options for the retryer
FutureOr<TData> Function() fn;
void Function()? _abort;
OnError<TError>? onError;
OnData<TData>? onSuccess;
void Function(int failureCount, TError error)? onFail;
void Function()? onPause;
void Function()? onContinue;
ShouldRetryFunction<TError>? retry;
RetryDelayFunction<TError>? retryDelay;
Retryer({
required this.fn,
void Function()? abort,
this.onError,
this.onSuccess,
this.onFail,
this.onPause,
this.onContinue,
this.retry,
this.retryDelay,
}) : _abort = abort,
failureCount = 0,
isPaused = false,
isResolved = false,
isTransportCancelable = false {
bool cancelRetry = false;
void Function({bool? revert, bool? silent})? cancelFn;
void Function([dynamic value])? continueFn;
cancel = ({bool? revert, bool? silent}) {
cancelFn?.call();
};
this.cancelRetry = () {
cancelRetry = true;
};
this.continueRetry = () {
cancelRetry = false;
};
this.continueFn = () => continueFn?.call();
completer = Completer<TData>();
// this.future = completer.future;
resolve(value) {
if (!this.isResolved) {
this.isResolved = true;
onSuccess?.call(value);
continueFn?.call();
if (!completer.isCompleted) completer.complete(value);
}
}
reject(value) {
if (!this.isResolved) {
this.isResolved = true;
onError?.call(value);
continueFn?.call();
if (!completer.isCompleted) completer.completeError(value);
}
}
pause() {
Completer pauseCompleter = Completer();
if (!pauseCompleter.isCompleted) continueFn = pauseCompleter.complete;
this.isPaused = true;
onPause?.call();
return pauseCompleter.future.then((val) {
continueFn = null;
this.isPaused = false;
onContinue?.call();
});
}
run() {
// Do nothing if already resolved
if (this.isResolved) {
return;
}
var promiseOrValue;
// Execute query
try {
promiseOrValue = fn();
} catch (error) {
promiseOrValue = Future.error(error);
}
// Create callback to cancel this fetch
cancelFn = ({bool? revert, bool? silent}) {
if (!this.isResolved) {
reject(new CancelledError(revert: revert, silent: silent));
abort?.call();
// Cancel transport if supported
if (isCancelable(promiseOrValue)) {
try {
promiseOrValue.cancel();
} catch (error) {}
}
}
};
// Check if the transport layer support cancellation
this.isTransportCancelable = isCancelable(promiseOrValue);
Future.value(promiseOrValue).then(resolve).catchError((error) {
// Stop if the fetch is already resolved
if (this.isResolved) return;
// Do we need to retry the request?
int _retry = retry?.call(failureCount, error) ?? 3;
double _retryDelay = retryDelay?.call(failureCount, error) ??
defaultRetryDelay(failureCount);
bool shouldRetry = _retry > 0 && _retry > failureCount;
if (cancelRetry || !shouldRetry) {
// We are done if the query does not need to be retried
reject(error);
return;
}
this.failureCount++;
// Notify on fail
onFail?.call(this.failureCount, error);
Future.delayed(Duration(milliseconds: _retryDelay.toInt()))
.then((val) async {
if (!await onlineManager.isOnline()) {
return pause();
}
}).then((val) {
if (cancelRetry) {
reject(error);
} else {
run();
}
});
});
}
// Start loop
run();
}
}
@@ -1,34 +0,0 @@
import 'package:meta/meta.dart';
//? using a single argument due to TypeCast Error cause queryObserver
//? listeners
void placeholder(a1) {}
abstract class Subscribable<TListener extends Function> {
@protected
List<TListener> listeners;
Subscribable() : listeners = [];
void Function() subscribe([TListener? listener]) {
listener ??= placeholder as TListener;
listeners.add(listener);
onSubscribe();
return () {
listeners = listeners.where((x) => x != listener).toList();
onUnsubscribe();
};
}
bool hasListeners() {
return listeners.isNotEmpty;
}
@protected
void onSubscribe() {}
@protected
void onUnsubscribe() {}
}
-175
View File
@@ -1,175 +0,0 @@
import 'package:fl_query/src/core/models.dart';
import 'package:fl_query/src/core/query.dart';
import 'package:fl_query/src/core/query_key.dart';
import 'package:collection/collection.dart';
import 'dart:math';
/// Default query keys hash function.
/// Dummy function just to fill the gaps for original react-query like
/// function body signatures
/// It is not required as a Standardized [QueryKey] data-class is used to
/// create the queryKey
String hashQueryKeyByOptions(
QueryKey queryKey,
QueryOptions? options,
) {
return options?.queryKeyHashFn?.call(queryKey) ?? queryKey.key;
}
enum QueryStatusFilter {
all,
active,
inactive,
none,
}
QueryStatusFilter mapQueryStatusFilter(
bool? active,
bool? inactive,
) {
if ((active == true && inactive == true) ||
(active == null && inactive == null)) {
return QueryStatusFilter.all;
} else if (active == false && inactive == false) {
return QueryStatusFilter.none;
} else {
// At this point, active|inactive can only be true|false or false|true
// so, when only one value is provided, the missing one has to be the negated value
bool isActive = active ?? !(inactive ?? false);
return isActive ? QueryStatusFilter.active : QueryStatusFilter.inactive;
}
}
bool matchQuery(
QueryFilters filters,
Query query, [
/// multiple queryKeys to find the query
QueryKey? queryKeys,
]) {
if (queryKeys != null) {
if (filters.exact == true &&
query.queryHash != hashQueryKeyByOptions(queryKeys, query.options))
return false;
else if (query.queryKey.key != queryKeys.key &&
!queryKeys.keyAsList.contains(query.queryKey.key) &&
!query.queryKey.keyAsList.contains(queryKeys.key)) return false;
}
QueryStatusFilter queryStatusFilter =
mapQueryStatusFilter(filters.active, filters.inactive);
if (queryStatusFilter == QueryStatusFilter.none) {
return false;
} else if (queryStatusFilter != QueryStatusFilter.all) {
bool isActive = query.isActive();
if (queryStatusFilter == QueryStatusFilter.active && !isActive) {
return false;
}
if (queryStatusFilter == QueryStatusFilter.inactive && isActive) {
return false;
}
}
if (filters.stale != null && query.isStale() != filters.stale) {
return false;
}
if (filters.fetching != null && query.isFetching() != filters.fetching) {
return false;
}
if (filters.predicate != null && !filters.predicate!(query)) {
return false;
}
return true;
}
void noop([e]) => null;
bool shallowEqualMap(Map? a, Map? b) {
if ((a != null && b == null) || (b != null && a == null)) {
return false;
}
for (final item in a!.entries) {
if (a[item.key] != b?[item.key]) return false;
}
return true;
}
/// This function returns `a` if `b` is deeply equal\
/// If not, it will replace any deeply equal children of `b` with those
/// of `a`\
/// This can be used for structural sharing between JSON values for example.
/// `a` & `b` can only be Type of [List] or [Map]
replaceEqualDeep(a, b) {
if (a == b) {
return a;
}
int aSize;
List bItems;
int bSize;
int equalItems = 0;
onEqual() => equalItems++;
var copy;
if (a is List && b is List) {
aSize = a.length;
bItems = b;
bSize = bItems.length;
copy = replaceEqualDeepList(a, b, onEqual);
} else if (a is Map && b is Map) {
aSize = a.keys.length;
bItems = b.keys.toList();
bSize = bItems.length;
copy = replaceEqualDeepMap(a, b, onEqual);
} else {
return b;
}
return aSize == bSize && equalItems == aSize ? a : copy;
}
Map replaceEqualDeepMap(Map a, Map b, void Function() onEqual) {
final copy = Map.from(a);
copy.clear();
for (final bEntry in b.entries) {
final aItem = a[bEntry.key];
copy[bEntry.key] =
aItem != null ? replaceEqualDeep(aItem, bEntry.value) : bEntry.value;
if (copy[bEntry.key] == aItem) {
onEqual();
}
}
return copy;
}
List replaceEqualDeepList(List a, List b, void Function() onEqual) {
final copy = List.of(a, growable: true);
copy.clear();
for (final bEntry in b.asMap().entries) {
final aItem = a.firstWhereIndexedOrNull((i, _) => i == bEntry.key);
final result =
aItem != null ? replaceEqualDeep(aItem, bEntry.value) : bEntry.value;
copy.add(result);
if (copy.last == aItem) {
onEqual();
}
}
return copy;
}
Duration timeUntilStale(DateTime updatedAt, [Duration? staleTime]) {
return Duration(
milliseconds: max<int>(
updatedAt
.add(staleTime ?? Duration.zero)
.difference(DateTime.now())
.inMilliseconds,
0,
),
);
}
typedef DataUpdateFunction<TInput, TOutput> = TOutput Function(TInput input);
+10
View File
@@ -0,0 +1,10 @@
import 'package:fl_query/query.dart';
Future<void> callQueryListeners<T>(Set<QueryListener<T>> listeners, T data) {
return Future.wait(listeners.map(
(listener) => Future.value(listener(data)),
));
// for (final listener in listeners) {
// await listener(data);
// }
}