removed old implementation

This commit is contained in:
Kingkor Roy Tirtho
2022-05-28 18:37:00 +06:00
parent b5a73f8cae
commit 8034a6a11e
36 changed files with 0 additions and 5415 deletions
-6
View File
@@ -1,6 +0,0 @@
{
"editor.tokenColorCustomizations": {
"comments": "",
"textMateRules": []
}
}
-3
View File
@@ -1,3 +0,0 @@
# fl-query
Flutter Query, the asynchronous data fetching & invalidation library for Flutter
-29
View File
@@ -1,29 +0,0 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.packages
build/
-10
View File
@@ -1,10 +0,0 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 77d935af4db863f6abd0b9c31c7e6df2a13de57b
channel: stable
project_type: package
-3
View File
@@ -1,3 +0,0 @@
## 0.0.1
* TODO: Describe initial release.
-1
View File
@@ -1 +0,0 @@
TODO: Add your license here.
-39
View File
@@ -1,39 +0,0 @@
<!--
This README describes the package. If you publish this package to pub.dev,
this README's contents appear on the landing page for your package.
For information about how to write a good package README, see the guide for
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
For general information about developing packages, see the Dart guide for
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
and the Flutter guide for
[developing packages and plugins](https://flutter.dev/developing-packages).
-->
TODO: Put a short description of the package here that helps potential users
know whether this package might be useful for them.
## Features
TODO: List what your package can do. Maybe include images, gifs, or videos.
## Getting started
TODO: List prerequisites and provide or point to information on how to
start using the package.
## Usage
TODO: Include short and useful examples for package users. Add longer examples
to `/example` folder.
```dart
const like = 'sample';
```
## Additional information
TODO: Tell users more about the package: where to find more information, how to
contribute to the package, how to file issues, what response they can expect
from the package authors, and more.
-4
View File
@@ -1,4 +0,0 @@
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
-6
View File
@@ -1,6 +0,0 @@
# Files and directories created by pub.
.dart_tool/
.packages
# Conventional directory for build output.
build/
-3
View File
@@ -1,3 +0,0 @@
## 1.0.0
- Initial version.
-1
View File
@@ -1 +0,0 @@
A simple command-line application.
@@ -1,30 +0,0 @@
# This file configures the static analysis results for your project (errors,
# warnings, and lints).
#
# This enables the 'recommended' set of lints from `package:lints`.
# This set helps identify many issues that may lead to problems when running
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
# style and format.
#
# If you want a smaller set of lints you can change this to specify
# 'package:lints/core.yaml'. These are just the most critical lints
# (the recommended set includes the core lints).
# The core lints are also what is used by pub.dev for scoring packages.
include: package:lints/recommended.yaml
# Uncomment the following section to specify additional rules.
# linter:
# rules:
# - camel_case_types
# analyzer:
# exclude:
# - path/to/excluded/files/**
# For more information about the core and recommended set of lints, see
# https://dart.dev/go/core-lints
# For additional information about configuring this file, see
# https://dart.dev/guides/language/analysis-options
@@ -1,57 +0,0 @@
import 'package:fl_query/fl_query.dart';
var todos = [
{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false},
{
"userId": 1,
"id": 2,
"title": "quis ut nam facilis et officia qui",
"completed": false
},
{"userId": 1, "id": 3, "title": "fugiat veniam minus", "completed": false},
{"userId": 1, "id": 4, "title": "et porro tempora", "completed": true},
{
"userId": 1,
"id": 5,
"title": "laboriosam mollitia et enim quasi adipisci quia provident illum",
"completed": false
},
{
"userId": 1,
"id": 6,
"title": "qui ullam ratione quibusdam voluptatem quia omnis",
"completed": false
},
];
void main() async {
try {
var key = QueryKey("TEST");
QueryClient queryClient = QueryClient();
queryClient.mount();
var data = await queryClient
.fetchQuery<Map<String, dynamic>, dynamic, Map<String, dynamic>>(
queryKey: key,
queryFn: (context) {
return Future.value(todos.first);
},
);
print("======FETCHED DATA======");
print(data);
print("======CACHED DATA======");
print(queryClient.getQueryData(key));
queryClient.setQueryData<Map<String, dynamic>>(key, (prevData) {
return {
...(prevData) ?? {},
"title": "Yehi aloh heh",
"completed": true,
};
});
print("======CACHED DATA======");
print(queryClient.getQueryData(key));
print("======STATE======");
print(queryClient.getQueryState(key)?.toJson());
} catch (e) {
print(e);
}
}
-124
View File
@@ -1,124 +0,0 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.8.2"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0"
crypto:
dependency: transitive
description:
name: crypto
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
fl_query:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
version: "0.0.1"
hive:
dependency: transitive
description:
name: hive
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5"
http:
dependency: transitive
description:
name: http
url: "https://pub.dartlang.org"
source: hosted
version: "0.13.4"
http_parser:
dependency: transitive
description:
name: http_parser
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.0"
internet_connection_checker:
dependency: transitive
description:
name: internet_connection_checker
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.1+3"
lints:
dependency: "direct dev"
description:
name: lints
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.1"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.2"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
uuid:
dependency: transitive
description:
name: uuid
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.5"
sdks:
dart: ">=2.15.1 <3.0.0"
-16
View File
@@ -1,16 +0,0 @@
name: example
description: A simple command-line application.
version: 1.0.0
# homepage: https://www.example.com
publish_to: none
environment:
sdk: '>=2.15.1 <3.0.0'
dependencies:
fl_query:
path: ../
dev_dependencies:
lints: ^1.0.0
-4
View File
@@ -1,4 +0,0 @@
library fl_query;
export 'package:fl_query/src/core/core.dart';
// export 'package:fl_query/src/core/framework.dart';
-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);
-31
View File
@@ -1,31 +0,0 @@
name: fl_query
description: A new Flutter package project.
version: 0.0.1
homepage: https://github.com/KRTirtho/fl-query
environment:
sdk: ">=2.15.1 <3.0.0"
# flutter: ">=1.17.0"
dependencies:
# flutter:
# sdk: flutter
meta: ^1.3.0
path: ^1.8.0
hive: ^2.0.0
http: ^0.13.0
collection: ^1.15.0
uuid: ^3.0.1
internet_connection_checker: ^0.0.1+3
dev_dependencies:
async: ^2.5.0
mockito: ^5.0.0
test: ^1.18.2
coverage: ^1.0.3
http_parser: ^4.0.0
lints: ^1.0.1
build_runner: ^2.1.7
# The following section is specific to Flutter.
# flutter:
-51
View File
@@ -1,51 +0,0 @@
import 'package:fl_query/src/core/core.dart';
import 'package:uuid/uuid.dart';
Uuid uuid = Uuid();
QueryKey queryKey() {
return QueryKey("query_${uuid.v4()}");
}
class SpyFn<T extends Function()> {
int _calls = 0;
late T _customFn;
int get calls => _calls;
T get customFn => _customFn;
SpyFn();
SpyFn.withFn(this._customFn);
fn([Function()? cb]) {
_calls++;
return () => cb?.call();
}
fn1([Function()? cb]) {
_calls++;
return (p0) => cb?.call();
}
fn2([Function()? cb]) {
_calls++;
return (p0, p1) => cb?.call();
}
fn3([Function()? cb]) {
_calls++;
return (p0, p1, p2) => cb?.call();
}
fn4([Function()? cb]) {
_calls++;
return (p0, p1, p2, p3) => cb?.call();
}
fn5([Function()? cb]) {
_calls++;
return (p0, p1, p2, p3, p4) => cb?.call();
}
}
Future<void> sleep(int ms) => Future.delayed(Duration(milliseconds: ms));
@@ -1,68 +0,0 @@
import 'package:fl_query/src/core/notify_manager.dart';
import 'package:test/expect.dart';
import 'package:test/scaffolding.dart';
class SpyNotifyManager extends NotifyManager {
SpyNotifyManager() : super();
int flushCall = 0;
@override
void flush() {
super.flush();
flushCall++;
}
}
void main() {
group("NotifyManager", () {
test(
"Should call _notifyFn in schedule When no callback is batched",
() async {
final NotifyManager notifyManager = NotifyManager();
int called = 0;
notifyManager.schedule(() => called++);
await Future.delayed(Duration(milliseconds: 1));
expect(called, equals(1));
},
);
test(
"Should call default _batchNotifyFn even When multiple level deep callbacks are registered",
() async {
final NotifyManager notifyManager = NotifyManager();
int level1 = 0;
int level2 = 0;
int level3 = 0;
callback() async {
await Future.delayed(Duration(milliseconds: 20));
level3++;
}
notifyManager.batch(() {
notifyManager.batch(() {
notifyManager.schedule(callback);
level2++;
});
level1++;
});
await Future.delayed(Duration(milliseconds: 30));
expect(level1, equals(1));
expect(level2, equals(1));
expect(level3, equals(1));
},
timeout: Timeout(Duration(minutes: 2)),
);
test("Should flush When Exception is thrown in a batched callback", () {
final SpyNotifyManager notifyManager = SpyNotifyManager();
try {
notifyManager.batch(() {
throw Exception("Damn an exception");
});
} catch (e) {}
expect(notifyManager.flushCall, equals(1));
});
});
}
@@ -1,116 +0,0 @@
import 'dart:async';
import 'package:fl_query/src/core/online_manager.dart';
import 'package:internet_connection_checker/internet_connection_checker.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:test/expect.dart';
import 'package:test/scaffolding.dart';
import './online_manager_test.mocks.dart';
@GenerateMocks([InternetConnectionChecker])
void main() {
group('OnlineManager', () {
late OnlineManager onlineManagerTest;
late StreamController<InternetConnectionStatus> statusController;
late MockInternetConnectionChecker connectionChecker;
setUp(() {
statusController = StreamController<InternetConnectionStatus>.broadcast();
statusController.add(InternetConnectionStatus.connected);
connectionChecker = MockInternetConnectionChecker();
when(connectionChecker.hasConnection)
.thenAnswer((_) => Future.value(true));
when(connectionChecker.hasListeners)
.thenReturn(statusController.hasListener);
when(connectionChecker.onStatusChange)
.thenAnswer((_) => statusController.stream);
onlineManagerTest = OnlineManager(connectionChecker);
});
tearDown(() {
statusController.close();
});
test(
'isOnline Should return true When InternetConnectionChecker.hasConnection is true',
() async {
bool online = await onlineManagerTest.isOnline();
expect(online, isTrue);
},
);
test(
"setEventListener Should use _online property When setOnline sets _online = false",
() async {
int count = 0;
setup(void Function(bool?) setOnline) {
Timer(Duration(milliseconds: 20), () {
count++;
setOnline(false);
});
return () {};
}
onlineManagerTest.setEventListener(setup);
await Future.delayed(Duration(milliseconds: 30));
expect(count, equals(1));
onlineManagerTest.isOnline().then((online) {
expect(online, isFalse);
});
},
);
test(
'setEventListener Should call previous remove handler When replacing an event listener',
() {
int cb1calls = 0;
int cb2calls = 0;
onlineManagerTest.setEventListener((_) => () => cb1calls++);
onlineManagerTest.setEventListener((_) => () => cb2calls++);
expect(cb1calls, equals(1));
expect(cb2calls, equals(0));
},
);
test(
'Should replace default window listener When a new event listener is set',
() {
// Should set the default event listener with window event listeners
final unsubscribe = onlineManagerTest.subscribe();
verify(connectionChecker.onStatusChange.listen).called(1);
// Should replace the window default event listener by a new one
// and it should call window.removeEventListener twice
onlineManagerTest.setEventListener((online) {
return () => null;
});
expect(connectionChecker.hasListeners, isFalse);
unsubscribe();
},
);
test('Should cancel StreamSubscription When last listener unsubscribes',
() {
final unsubscribe1 = onlineManager.subscribe(() => null);
final unsubscribe2 = onlineManager.subscribe(() => null);
verify(connectionChecker.onStatusChange.listen).called(1);
unsubscribe1();
expect(connectionChecker.hasListeners, isTrue);
unsubscribe2();
expect(connectionChecker.hasListeners, isFalse);
}, skip: true);
test('should keep setup function even if last listener unsubscribes', () {
int count = 0;
onlineManager.setEventListener((_) => () => count++);
final unsubscribe1 = onlineManagerTest.subscribe(() => null);
expect(count, equals(1));
unsubscribe1();
final unsubscribe2 = onlineManager.subscribe(() => null);
expect(count, equals(2));
unsubscribe2();
}, skip: true);
});
}
@@ -1,82 +0,0 @@
// Mocks generated by Mockito 5.1.0 from annotations
// in fl_query/test/src/core/online_manager_test.dart.
// Do not manually edit this file.
import 'dart:async' as _i3;
import 'package:internet_connection_checker/internet_connection_checker.dart'
as _i2;
import 'package:mockito/mockito.dart' as _i1;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
// ignore_for_file: avoid_setters_without_getters
// ignore_for_file: comment_references
// ignore_for_file: implementation_imports
// ignore_for_file: invalid_use_of_visible_for_testing_member
// ignore_for_file: prefer_const_constructors
// ignore_for_file: unnecessary_parenthesis
// ignore_for_file: camel_case_types
class _FakeDuration_0 extends _i1.Fake implements Duration {}
class _FakeAddressCheckResult_1 extends _i1.Fake
implements _i2.AddressCheckResult {}
/// A class which mocks [InternetConnectionChecker].
///
/// See the documentation for Mockito's code generation for more information.
class MockInternetConnectionChecker extends _i1.Mock
implements _i2.InternetConnectionChecker {
MockInternetConnectionChecker() {
_i1.throwOnMissingStub(this);
}
@override
List<_i2.AddressCheckOptions> get addresses =>
(super.noSuchMethod(Invocation.getter(#addresses),
returnValue: <_i2.AddressCheckOptions>[])
as List<_i2.AddressCheckOptions>);
@override
set addresses(List<_i2.AddressCheckOptions>? _addresses) =>
super.noSuchMethod(Invocation.setter(#addresses, _addresses),
returnValueForMissingStub: null);
@override
Duration get checkInterval =>
(super.noSuchMethod(Invocation.getter(#checkInterval),
returnValue: _FakeDuration_0()) as Duration);
@override
set checkInterval(Duration? _checkInterval) =>
super.noSuchMethod(Invocation.setter(#checkInterval, _checkInterval),
returnValueForMissingStub: null);
@override
_i3.Future<bool> get hasConnection =>
(super.noSuchMethod(Invocation.getter(#hasConnection),
returnValue: Future<bool>.value(false)) as _i3.Future<bool>);
@override
_i3.Future<_i2.InternetConnectionStatus> get connectionStatus =>
(super.noSuchMethod(Invocation.getter(#connectionStatus),
returnValue: Future<_i2.InternetConnectionStatus>.value(
_i2.InternetConnectionStatus.connected))
as _i3.Future<_i2.InternetConnectionStatus>);
@override
_i3.Stream<_i2.InternetConnectionStatus> get onStatusChange =>
(super.noSuchMethod(Invocation.getter(#onStatusChange),
returnValue: Stream<_i2.InternetConnectionStatus>.empty())
as _i3.Stream<_i2.InternetConnectionStatus>);
@override
bool get hasListeners =>
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false)
as bool);
@override
bool get isActivelyChecking =>
(super.noSuchMethod(Invocation.getter(#isActivelyChecking),
returnValue: false) as bool);
@override
_i3.Future<_i2.AddressCheckResult> isHostReachable(
_i2.AddressCheckOptions? options) =>
(super.noSuchMethod(Invocation.method(#isHostReachable, [options]),
returnValue: Future<_i2.AddressCheckResult>.value(
_FakeAddressCheckResult_1()))
as _i3.Future<_i2.AddressCheckResult>);
}
@@ -1,300 +0,0 @@
import 'package:fl_query/src/core/core.dart';
import 'package:fl_query/src/core/query_cache.dart';
import 'package:fl_query/src/core/retryer.dart';
import 'package:test/expect.dart';
import 'package:test/scaffolding.dart';
import '../../helpers/utils.dart';
void main() {
group('QueryCache', () {
late QueryClient queryClient;
late QueryCache queryCache;
setUp(() {
queryClient = new QueryClient();
queryCache = queryClient.getQueryCache();
});
tearDown(() {
queryClient.clear();
});
group('subscribe', () {
test('Should pass the correct query', () async {
final QueryKey key = queryKey();
var _event;
subscriber(event) {
_event ??= event;
}
final unsubscribe = queryCache.subscribe(subscriber);
final Map<String, dynamic> data = {'foo': "foo"};
queryClient.setQueryData(key, (_) => data);
final query = queryCache.find(key);
await Future.delayed(Duration(milliseconds: 1));
expect(_event, isA<QueryCacheNotifyEvent>());
expect(
(_event as QueryCacheNotifyEvent).type,
QueryCacheNotifyEventType.queryAdded,
);
expect(
(_event as QueryCacheNotifyEvent).query,
same(query),
);
unsubscribe();
});
test('Should notify listeners When new query is added', () async {
final key = queryKey();
late bool called;
callback(_) {
called = true;
}
queryCache.subscribe(callback);
queryClient.prefetchQuery(
queryKey: key,
queryFn: (_) async => {'data': "data"},
);
await Future.delayed(Duration(milliseconds: 100));
expect(called, isTrue);
});
test('Should include the queryCache and query When notifying listeners',
() async {
final key = queryKey();
QueryCacheNotifyEvent? _event;
callback(event) => _event ??= event;
queryCache.subscribe(callback);
queryClient.prefetchQuery(
queryKey: key,
queryFn: (_) => {'data': "data"},
);
final query = queryCache.find(key);
await Future.delayed(Duration(milliseconds: 100));
expect(_event, isA<QueryCacheNotifyEvent>());
expect(_event?.type, QueryCacheNotifyEventType.queryAdded);
expect(_event?.query, same(query));
});
test('Should notify subscribers When new query with initialData is added',
() async {
final key = queryKey();
late bool called;
callback(_) => called = true;
queryCache.subscribe(callback);
queryClient
.prefetchQuery<Map<String, dynamic>, dynamic, Map<String, dynamic>>(
queryKey: key,
queryFn: (_) => {'data': "Data"},
options: FetchQueryOptions(initialData: {"data": "initial-data"}),
);
await Future.delayed(Duration(milliseconds: 100));
expect(called, isTrue);
});
});
group('find', () {
test('Should filter correctly', () async {
final key = queryKey();
await queryClient.prefetchQuery(
queryKey: key,
queryFn: (_) => {"data": 'data1'},
);
final query = queryCache.find(key);
expect(query, isNotNull);
});
test(
'Should filter correctly When called with exact set to false',
() async {
final key = queryKey();
await queryClient.prefetchQuery(
queryKey: key,
queryFn: (_) => {"data": 'data1'},
);
final query = queryCache.find(key, QueryFilters(exact: false));
expect(query, isNotNull);
},
);
});
group('findAll', () {
test('Should filter correctly', () async {
final key1 = queryKey();
final key2 = queryKey();
final key3 = QueryKey.fromList(['posts', "1"]);
await queryClient.prefetchQuery(
queryKey: key1,
queryFn: (_) => {"data": 'data1'},
);
await queryClient.prefetchQuery(
queryKey: key2,
queryFn: (_) => {"data": 'data2'},
);
await queryClient.prefetchQuery(
queryKey: key3,
queryFn: (_) => {"data": 'data4'},
);
await queryClient.invalidateQueries(queryKeys: key2);
final query1 = queryCache.find(key1);
final query2 = queryCache.find(key2);
final query4 = queryCache.find(key3);
expect(queryCache.findAll(key1), equals([query1]));
expect(queryCache.findAll(), equals([query1, query2, query4]));
expect(
queryCache.findAll(key1, QueryFilters(active: false)),
equals([query1]),
);
expect(
queryCache.findAll(key1, QueryFilters(active: true)), equals([]));
expect(queryCache.findAll(key1, QueryFilters(stale: true)), equals([]));
expect(
queryCache.findAll(key1, QueryFilters(stale: false)),
equals([query1]),
);
expect(
queryCache.findAll(key1, QueryFilters(stale: false, active: true)),
equals([]),
);
expect(
queryCache.findAll(key1, QueryFilters(active: false, stale: false)),
equals([query1]),
);
expect(
queryCache.findAll(
key1,
QueryFilters(active: false, stale: false, exact: true),
),
equals([query1]),
);
expect(queryCache.findAll(key2), equals([query2]));
expect(
queryCache.findAll(key2, QueryFilters(stale: null)),
equals([query2]),
);
expect(
queryCache.findAll(key2, QueryFilters(stale: true)),
equals([query2]),
);
expect(
queryCache.findAll(key2, QueryFilters(stale: false)),
equals([]),
);
expect(
queryCache.findAll(
null,
QueryFilters(predicate: (query) => query == query4),
),
equals([query4]),
);
expect(queryCache.findAll(QueryKey('posts')), equals([query4]));
});
test('Should return all the queries When no filters are defined',
() async {
final key1 = queryKey();
final key2 = queryKey();
await queryClient.prefetchQuery(
queryKey: key1,
queryFn: (_) => {"data": 'data1'},
);
await queryClient.prefetchQuery(
queryKey: key2,
queryFn: (_) {
return {"data": 'data2'};
},
);
expect(queryCache.findAll().length, 2);
});
});
group('QueryCacheConfig.onError', () {
test('should be called when a query errors', () async {
final key = queryKey();
var errorArg;
var queryArg;
onError(error, query) {
errorArg = error;
queryArg = query;
}
final testCache = new QueryCache(onError: onError);
final testClient = new QueryClient(queryCache: testCache);
await testClient
.prefetchQuery<Map<String, dynamic>, dynamic, Map<String, dynamic>>(
queryKey: key, queryFn: (_) => Future.error('error'));
final query = testCache.find(key);
expect(errorArg, equals("error"));
expect(queryArg, equals(query));
});
});
group('QueryCacheConfig.onSuccess', () {
test('should be called when a query is successful', () async {
final key = queryKey();
var dataArg;
var queryArg;
onData(data, query) {
dataArg = data;
queryArg = query;
}
final testCache = new QueryCache(onData: onData);
final testClient = new QueryClient(queryCache: testCache);
await testClient
.prefetchQuery<Map<String, dynamic>, dynamic, Map<String, dynamic>>(
queryKey: key,
queryFn: (_) => Future.value({"data": 5}),
);
final query = testCache.find(key);
expect(dataArg, equals({"data": 5}));
expect(queryArg, equals(query));
});
});
group('QueryCache.add', () {
test('should not try to add a query already added to the cache',
() async {
final key = queryKey();
final hash = key.key;
await queryClient.prefetchQuery(
queryKey: key, queryFn: (_) => {"data": 'data1'});
// Directly add the query from the cache
// to simulate a race condition
final query = queryCache.queriesMap[hash] as Query;
// No error should be thrown when trying to add the query
queryCache.add(query);
expect(queryCache.queries.length, 1);
// Clean-up to avoid an error when queryClient.clear()
queryCache.remove(query);
});
});
group('QueryCache.remove', () {
test('should not try to remove a query already removed from the cache',
() async {
final key = queryKey();
final hash = key.key;
await queryClient.prefetchQuery(
queryKey: key, queryFn: (_) => {"data": 'data1'});
// Directly remove the query from the cache
// to simulate a race condition
final query = queryCache.queriesMap[hash] as Query;
queryCache.queriesMap.remove(hash);
// No error should be thrown when trying to remove the query
expect(() => queryCache.remove(query), isNot(throwsException));
});
});
});
}
@@ -1,912 +0,0 @@
import 'dart:async';
import 'package:fl_query/src/core/core.dart';
import 'package:fl_query/src/core/query_observer.dart';
import 'package:test/test.dart';
import '../../helpers/utils.dart';
typedef QueryFn = FutureOr<Map<String, dynamic>> Function(
QueryFunctionContext<dynamic>);
void main() {
group('QueryObserver', () {
late QueryClient queryClient;
setUp(() {
queryClient = QueryClient();
queryClient.mount();
});
tearDown(() {
queryClient.clear();
});
test('should trigger a fetch when subscribed', () async {
final key = queryKey();
;
int calls = 0;
queryFn(context) {
calls++;
return {"data": "data1"};
}
final observer = QueryObserver(
queryClient,
QueryObserverOptions(queryKey: key, queryFn: queryFn),
);
final unsubscribe = observer.subscribe();
await Future.delayed(Duration(milliseconds: 1));
unsubscribe();
expect(calls, 1);
});
test('should notify when switching query', () async {
final key1 = queryKey();
final key2 = queryKey();
final List<QueryObserverResult> results = [];
final observer = QueryObserver(
queryClient,
QueryObserverOptions(
queryKey: key1,
queryFn: (_) => {"data": 1},
),
);
final unsubscribe = observer.subscribe((result) {
results.add(result);
});
await Future.delayed(Duration(milliseconds: 1));
observer.setOptions(
QueryObserverOptions(queryKey: key2, queryFn: (_) => {"data": 2}),
);
await Future.delayed(Duration(milliseconds: 2));
unsubscribe();
expect(results.length, 4);
expect(results[0].data, isNull);
expect(results[0].status, QueryStatus.loading);
expect(results[1].data, {"data": 1});
expect(results[1].status, QueryStatus.success);
expect(results[2].data, isNull);
expect(results[2].status, QueryStatus.loading);
expect(results[3].data, {"data": 2});
expect(results[3].status, QueryStatus.success);
});
test('should be able to fetch with a selector', () async {
final key = queryKey();
;
final observer = QueryObserver<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryClient,
QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryKey: key,
queryFn: (_) => {"count": 1},
select: (data) => ({"myCount": data?["count"]}),
));
QueryObserverResult? observerResult;
final unsubscribe = observer.subscribe((result) {
observerResult = result;
});
await Future.delayed(Duration(milliseconds: 1));
unsubscribe();
expect(
observerResult?.data,
equals({"myCount": 1}),
);
});
test('should be able to fetch with a selector using the fetch method',
() async {
final key = queryKey();
;
final observer = QueryObserver<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryClient,
QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryKey: key,
queryFn: (_) => {"count": 1},
select: (data) => ({"myCount": data?["count"]}),
));
final observerResult = await observer.refetch();
expect(observerResult?.data, equals({"myCount": 1}));
});
test('should run the selector again if the data changed', () async {
final key = queryKey();
;
int count = 0;
final observer = QueryObserver<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryClient,
QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryKey: key,
queryFn: (_) => Map.from({"count": count}),
select: (data) {
count++;
return Map.from({"myCount": data?["count"]});
},
));
final observerResult1 = await observer.refetch();
final observerResult2 = await observer.refetch();
expect(count, 2);
expect(observerResult1?.data, equals({"myCount": 0}));
expect(observerResult2?.data, equals({"myCount": 1}));
});
test('should run the selector again if the selector changed', () async {
final key = queryKey();
;
int count = 0;
final List<QueryObserverResult> results = [];
final queryFn = (_) => ({"count": 1});
select1(data) {
count++;
return {"myCount": data?["count"]};
}
select2(_data) {
count++;
return {"myCount": 99};
}
final observer = new QueryObserver(
queryClient,
QueryObserverOptions(
queryKey: key,
queryFn: queryFn,
select: select1,
));
final unsubscribe = observer.subscribe((result) {
results.add(result);
});
await Future.delayed(Duration(milliseconds: 1));
observer.setOptions(QueryObserverOptions(
queryKey: key,
queryFn: queryFn,
select: select2,
));
await Future.delayed(Duration(milliseconds: 1));
//! Currently causing an extra call for refetch
//! select shouldn't be called when refetch is called
await observer.refetch();
unsubscribe();
expect(count, 2);
expect(results.length, 5);
expect(results.first.status, QueryStatus.loading);
expect(results.first.isFetching, isTrue);
expect(results.first.data, isNull);
expect(results[1].status, QueryStatus.success);
expect(results[1].isFetching, false);
expect(results[1].data, {"myCount": 1});
expect(results[2].status, QueryStatus.success);
expect(results[2].isFetching, false);
expect(results[2].data, {"myCount": 99});
expect(results[3].status, QueryStatus.success);
expect(results[3].isFetching, true);
expect(results[3].data, {"myCount": 99});
expect(results.last.status, QueryStatus.success);
expect(results.last.isFetching, false);
expect(results.last.data, {"myCount": 99});
});
test(
'should not run the selector again if the data and selector did not change',
() async {
final key = queryKey();
;
int count = 0;
final List<QueryObserverResult> results = [];
final queryFn = (_) => {"count": 1};
select(data) {
count++;
return {"myCount": data["count"]};
}
final observer = new QueryObserver(
queryClient,
QueryObserverOptions(
queryKey: key,
queryFn: queryFn,
select: select,
));
final unsubscribe = observer.subscribe((result) {
results.add(result);
});
await Future.delayed(Duration(milliseconds: 1));
observer.setOptions(QueryObserverOptions(
queryKey: key,
queryFn: queryFn,
select: select,
));
await Future.delayed(Duration(milliseconds: 1));
await observer.refetch();
unsubscribe();
expect(count, 1);
expect(results.length, 4);
expect(results.first.status, QueryStatus.loading);
expect(results.first.isFetching, isTrue);
expect(results.first.data, isNull);
expect(results[1].status, QueryStatus.success);
expect(results[1].isFetching, false);
expect(results[1].data, {"myCount": 1});
expect(results[2].status, QueryStatus.success);
expect(results[2].isFetching, true);
expect(results[2].data, {"myCount": 1});
expect(results.last.status, QueryStatus.success);
expect(results.last.isFetching, false);
expect(results.last.data, {"myCount": 1});
});
test('should not run the selector again if the data did not change',
() async {
final key = queryKey();
;
int count = 0;
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryClient,
QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryKey: key,
queryFn: (_) => {"count": 1},
select: (data) {
count++;
return {"myCount": data?["count"]};
},
),
);
final observerResult1 = await observer.refetch();
final observerResult2 = await observer.refetch();
expect(count, 1);
expect(observerResult1?.data, equals({"myCount": 1}));
expect(observerResult2?.data, equals({"myCount": 1}));
});
test('should always run the selector again if selector throws an error',
() async {
final key = queryKey();
;
final List<QueryObserverResult> results = [];
select(data) {
throw new Exception('selector error');
}
queryFn(_) => ({"count": 1});
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryClient,
QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryKey: key,
queryFn: queryFn,
select: select,
));
final unsubscribe = observer.subscribe((result) {
results.add(result);
});
await Future.delayed(Duration(milliseconds: 1));
await observer.refetch();
unsubscribe();
expect(results.length, 5);
expect(results.first.status, QueryStatus.loading);
expect(results.first.isFetching, isTrue);
expect(results.first.data, isNull);
expect(results[1].status, QueryStatus.error);
expect(results[1].isFetching, false);
expect(results[1].data, isNull);
expect(results[2].status, QueryStatus.error);
expect(results[2].isFetching, true);
expect(results[2].data, isNull);
expect(results[3].status, QueryStatus.error);
expect(results[3].isFetching, false);
expect(results[3].data, isNull);
expect(results.last.status, QueryStatus.error);
expect(results.last.isFetching, false);
expect(results.last.data, isNull);
});
test('should structurally share the selector', () async {
final key = queryKey();
;
int count = 0;
final observer = new QueryObserver(
queryClient,
QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryKey: key,
queryFn: (_) => {"count": ++count},
select: (data) {
return {"myCount": 1};
},
));
final observerResult1 = await observer.refetch();
final observerResult2 = await observer.refetch();
expect(count, 2);
expect(observerResult1?.data, isNotNull);
expect(observerResult1?.data, equals(observerResult2?.data));
});
test('should not trigger a fetch when subscribed and disabled', () async {
int count = 0;
final key = queryKey();
;
final observer = new QueryObserver(
queryClient,
QueryObserverOptions(
queryKey: key,
queryFn: (_) {
count++;
return {"data": null};
},
enabled: false,
));
final unsubscribe = observer.subscribe();
await Future.delayed(Duration(milliseconds: 1));
unsubscribe();
expect(count, 0);
});
test('should not trigger a fetch when not subscribed', () async {
int count = 0;
final key = queryKey();
;
new QueryObserver(
queryClient,
QueryObserverOptions(
queryKey: key,
queryFn: (_) {
count++;
return {"data": null};
},
));
await Future.delayed(Duration(milliseconds: 1));
expect(count, 0);
});
test('should be able to watch a query without defining a query function',
() async {
int count = 0;
int subscribeCount = 0;
final key = queryKey();
;
queryFn(_) {
count++;
return {"data": null};
}
final observer = QueryObserver<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryClient,
QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryKey: key,
queryFn: queryFn,
enabled: false,
));
final unsubscribe = observer.subscribe((_) {
subscribeCount++;
});
await queryClient.fetchQuery(queryKey: key, queryFn: queryFn);
unsubscribe();
expect(count, 1);
expect(subscribeCount, 2);
});
test('should accept unresolved query config in update function', () async {
final key = queryKey();
;
final observer = QueryObserver<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryClient,
QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryKey: key,
enabled: false,
));
final List<QueryObserverResult> results = [];
final unsubscribe = observer.subscribe((x) {
results.add(x);
});
observer.setOptions(
QueryObserverOptions(
enabled: false,
staleTime: Duration(milliseconds: 10),
),
);
int count = 0;
queryFn(_) {
count++;
return {"data": null};
}
await queryClient.fetchQuery(queryKey: key, queryFn: queryFn);
await sleep(100);
unsubscribe();
expect(count, 1);
expect(results.length, 3);
expect(results[0].isStale, true);
expect(results[1].isStale, false);
expect(results[2].isStale, true);
});
test('should be able to handle multiple subscribers', () async {
final key = queryKey();
int count = 0;
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryClient,
QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryKey: key,
enabled: false,
));
final List<QueryObserverResult> results1 = [];
final List<QueryObserverResult> results2 = [];
final unsubscribe1 = observer.subscribe((x) {
results1.add(x);
});
final unsubscribe2 = observer.subscribe((x) {
results2.add(x);
});
await queryClient
.fetchQuery<Map<String, dynamic>, dynamic, Map<String, dynamic>>(
queryKey: key,
queryFn: (_) {
count++;
return {"data": false};
},
);
await sleep(50);
unsubscribe1();
unsubscribe2();
expect(count, 1);
expect(results1.length, 2);
expect(results2.length, 2);
expect(results1[0].data?["data"], isNull);
expect(results1[1].data?["data"], isFalse);
expect(results2[0].data?["data"], isNull);
expect(results2[1].data?["data"], isFalse);
});
test('should be able to resolve a promise', () async {
final key = queryKey();
int count = 0;
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryClient,
QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryKey: key,
enabled: false,
));
QueryObserverResult<Map<String, dynamic>, dynamic>? value;
observer.getNextResult().then((x) {
value = x;
});
queryClient
.prefetchQuery<Map<String, dynamic>, dynamic, Map<String, dynamic>>(
queryKey: key,
queryFn: (_) {
count++;
return {"data": "a data"};
});
await sleep(50);
expect(count, 1);
expect(value?.data?["data"], "a data");
});
test('should be able to resolve a promise with an error', () async {
final key = queryKey();
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryClient,
QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryKey: key,
enabled: false,
));
var error;
await observer.getNextResult(true).catchError((e) async {
error = e;
return e;
});
await queryClient
.prefetchQuery<Map<String, dynamic>, dynamic, Map<String, dynamic>>(
queryKey: key, queryFn: (_) => Future.error('reject'));
await sleep(50);
expect(error, 'reject');
}, skip: true);
test('should stop retry when unsubscribing', () async {
int count = 0;
final key = queryKey();
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryClient,
QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryKey: key,
queryFn: (_) {
count++;
return Future.error({"data": 'reject'});
},
retry: (_, __) => 10,
retryDelay: (_, __) => 50,
),
);
final unsubscribe = observer.subscribe();
await sleep(70);
unsubscribe();
await sleep(200);
expect(count, 2);
}, skip: true);
test('should clear interval when unsubscribing to a refetchInterval query',
() async {
final key = queryKey();
final fetchData =
(_) => Future<Map<String, dynamic>>.error({'data': null});
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryClient,
QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryKey: key,
queryFn: fetchData,
cacheTime: Duration.zero,
refetchInterval: (_, __) => Duration(milliseconds: 1),
));
final unsubscribe = observer.subscribe();
// @ts-expect-error
expect(observer.refetchInterval, isNotNull);
unsubscribe();
// @ts-expect-error
expect(observer.refetchInterval, isNull);
await sleep(10);
expect(queryClient.getQueryCache().find(key), isNull);
});
test(
'uses placeholderData as non-cache data when loading a query with no data',
() async {
final key = queryKey();
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryClient,
QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryKey: key,
queryFn: (_) => {'data': 'data'},
placeholderData: {"data": 'placeholder'},
));
final result = observer.getCurrentResult();
expect(result?.status, QueryStatus.success);
final List<QueryObserverResult> results = [];
final unsubscribe = observer.subscribe((x) {
results.add(x);
});
await sleep(10);
unsubscribe();
expect(results.length, 2);
expect(results.first.status, QueryStatus.success);
expect(results.first.data, equals({"data": 'placeholder'}));
expect(results.last.status, QueryStatus.success);
expect(results.last.data, equals({'data': 'data'}));
});
test(
'the retryer should not throw an error when reject if the retrier is already resolved',
() async {
final key = queryKey();
int count = 0;
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryClient,
QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryKey: key,
queryFn: (_) {
count++;
return Future.error({'reject': count});
},
retry: (_, __) => 1,
retryDelay: (_, __) => 20,
));
final unsubscribe = observer.subscribe();
// Simulate a race condition when an unsubscribe and a retry occur.
await sleep(20);
unsubscribe();
// A second reject is triggered for the retry
// but the retryer has already set isResolved to true
// so it does nothing and no error is thrown
// Should not log an error
queryClient.clear();
await sleep(40);
expect(true, true);
// expect(consoleMock).not.toHaveBeenNthCalledWith(1, 'reject 1')
// consoleMock.mockRestore()
}, skip: true);
test('getCurrentQuery should return the current query', () async {
final key = queryKey();
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryClient,
QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryKey: key, queryFn: (_) => {'data': 'data'}));
expect(observer.getCurrentQuery().queryKey, key);
});
test('should throw an error if throwOnError option is true', () async {
final key = queryKey();
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryClient,
QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryKey: key,
queryFn: (_) => Future.error({'error': 'error'}),
retry: (_, __) => 0,
));
var error = null;
try {
await observer.refetch(options: RefetchOptions(throwOnError: true));
} catch (err) {
error = err;
}
expect(error, equals({'error': 'error'}));
});
test(
'should not refetch in background if refetchIntervalInBackground is false',
() async {
final key = queryKey();
final spy = SpyFn();
// focusManager.setFocused(false)
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryClient,
QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryKey: key,
queryFn: spy.fn1(),
refetchIntervalInBackground: false,
refetchInterval: (_, __) => Duration(milliseconds: 10),
));
final unsubscribe = observer.subscribe();
await sleep(30);
expect(spy.calls, 1);
// Clean-up
unsubscribe();
// focusManager.setFocused(true)
});
test(
'should not use replaceEqualDeep for select value when structuralSharing option is true',
() async {
final key = queryKey();
final data = {"value": 'data'};
final selectedData = {"value": 'data'};
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryClient,
QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryKey: key,
queryFn: (_) => data,
select: (_) => data,
));
final unsubscribe = observer.subscribe();
await sleep(10);
expect(observer.getCurrentResult()?.data, data);
observer.setOptions(QueryObserverOptions(
queryKey: key,
queryFn: (_) => data,
structuralSharing: false,
select: (_) => selectedData,
));
await observer.refetch(filters: RefetchableQueryFilters(queryKey: key));
expect(observer.getCurrentResult()?.data, selectedData);
unsubscribe();
});
test('select function error using placeholderdata should log an error', () {
final key = queryKey();
QueryObserver<Map<String, dynamic>, dynamic, Map<String, dynamic>,
Map<String, dynamic>>(
queryClient,
QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryKey: key,
queryFn: (_) => {"data": 'data'},
placeholderData: {"data": 'placeholderdata'},
select: (_) {
throw new Exception('error');
},
));
expect(true, true);
// expect(consoleMock).toHaveBeenNthCalledWith(1, new Error('error'))
// consoleMock.mockRestore()
});
test(
'should not use replaceEqualDeep for select value when structuralSharing option is true and placeholderdata is defined',
() {
final key = queryKey();
final data = {"value": 'data'};
final selectedData1 = {"value": 'data'};
final selectedData2 = {"value": 'data'};
final placeholderData1 = {"value": 'data'};
final placeholderData2 = {"value": 'data'};
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryClient,
QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryKey: key,
queryFn: (_) => data,
select: (_) => data,
));
observer.setOptions(QueryObserverOptions(
queryKey: key,
queryFn: (_) => data,
select: (_) {
return selectedData1;
},
placeholderData: placeholderData1,
));
observer.setOptions(QueryObserverOptions(
queryKey: key,
queryFn: (_) => data,
select: (_) {
return selectedData2;
},
placeholderData: placeholderData2,
structuralSharing: false,
));
expect(observer.getCurrentResult()?.data, equals(selectedData2));
});
test(
'should not use an undefined value returned by select as placeholderdata',
() {
final key = queryKey();
final data = {"value": 'data'};
final selectedData = {"value": 'data'};
final placeholderData1 = {"value": 'data'};
final placeholderData2 = {"value": 'data'};
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryClient,
QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryKey: key,
queryFn: (_) => data,
select: (_) => data,
));
observer.setOptions(QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryKey: key,
queryFn: (_) => data,
select: (_) {
return selectedData;
},
placeholderData: placeholderData1,
));
expect(observer.getCurrentResult()?.isPlaceholderData, isTrue);
observer.setOptions(QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryKey: key,
queryFn: (_) => data,
select: (_) {
return {};
},
placeholderData: placeholderData2,
));
expect(observer.getCurrentResult()?.isPlaceholderData, isFalse);
});
test(
'updateResult should not notify cache listeners if cache option is false',
() async {
final key = queryKey();
final data1 = {"value": 'data 1'};
final data2 = {"value": 'data 2'};
await queryClient.prefetchQuery(queryKey: key, queryFn: (_) => data1);
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryClient,
QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(queryKey: key));
await queryClient.prefetchQuery(queryKey: key, queryFn: (_) => data2);
final spy = SpyFn();
final unsubscribe = queryClient.getQueryCache().subscribe(spy.fn1());
observer.updateResult(NotifyOptions(cache: false));
expect(spy.calls, 0);
unsubscribe();
});
test(
'should not notify observer when the stale timeout expires and the current result is stale',
() async {
final key = queryKey();
final queryFn = (_) => {'data': "data"};
await queryClient.prefetchQuery(queryKey: key, queryFn: queryFn);
final observer = new QueryObserver<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryClient,
QueryObserverOptions<Map<String, dynamic>, dynamic,
Map<String, dynamic>, Map<String, dynamic>>(
queryKey: key,
queryFn: queryFn,
staleTime: Duration(milliseconds: 20),
));
final spy = SpyFn();
final unsubscribe = observer.subscribe(spy.fn1());
await queryClient.refetchQueries(queryKeys: key);
await sleep(10);
// Force isStale to true
// because no use case has been found to reproduce this condition
// @ts-ignore
// observer.getCurrentResult().isStale = true;
await sleep(30);
expect(spy.calls, 0);
unsubscribe();
});
});
}
@@ -1,320 +0,0 @@
import 'package:fl_query/src/core/utils.dart';
import 'package:test/expect.dart';
import 'package:test/scaffolding.dart';
void main() {
group('core/utils', () {
group('replaceEqualDeep', () {
test(
'Should return the previous value When the next value is an equal primitive',
() {
expect(replaceEqualDeep(1, 1), equals(1));
expect(replaceEqualDeep('1', '1'), equals('1'));
expect(replaceEqualDeep(true, true), true);
expect(replaceEqualDeep(false, false), false);
expect(replaceEqualDeep(null, null), null);
});
test(
'Should return the next value When the previous value is a different value',
() {
expect(replaceEqualDeep(1, 0), equals(0));
expect(replaceEqualDeep(1, 2), equals(2));
expect(replaceEqualDeep('1', '2'), equals('2'));
expect(replaceEqualDeep(true, false), equals(false));
expect(replaceEqualDeep(false, true), equals(true));
});
test(
'Should return the next value When the previous value is a different type',
() {
final array = [1];
final object = {"a": "a"};
expect(replaceEqualDeep(0, null), equals(null));
expect(replaceEqualDeep(null, 0), equals(0));
expect(replaceEqualDeep(2, null), equals(null));
expect(replaceEqualDeep(null, 2), equals(2));
expect(replaceEqualDeep({}, null), equals(null));
expect(replaceEqualDeep([], null), equals(null));
expect(replaceEqualDeep(array, object), equals(object));
expect(replaceEqualDeep(object, array), equals(array));
});
test(
'Should return the previous value When the next value is an equal array',
() {
final prev = [1, 2];
final next = [1, 2];
expect(replaceEqualDeep(prev, next), equals(prev));
});
test(
'Should return a copy When the previous value is a different array subset',
() {
final prev = [1, 2];
final next = [1, 2, 3];
final result = replaceEqualDeep(prev, next);
expect(result, equals(next));
expect(result, isNot(equals(prev)));
});
test(
'Should return the previous value When the next value is an equal empty array',
() {
final prev = [];
final next = [];
expect(replaceEqualDeep(prev, next), equals(prev));
});
test(
'Should return the previous value When the next value is an equal empty object',
() {
final prev = {};
final next = {};
expect(replaceEqualDeep(prev, next), equals(prev));
});
test(
'Should return the previous value When the next value is an equal object',
() {
final prev = {"a": 'a'};
final next = {"a": 'a'};
expect(replaceEqualDeep(prev, next), equals(prev));
});
test('Should replace different values in objects', () {
final prev = {
"a": {"b": 'b'},
"c": 'c'
};
final next = {
"a": {"b": 'b'},
"c": 'd'
};
final result = replaceEqualDeep(prev, next);
expect(result, equals(next));
expect(result, isNot(prev));
expect(result["a"], equals(prev["a"]));
expect(result["c"], equals(next["c"]));
});
test('Should replace different values in arrays', () {
final prev = [
1,
{"a": 'a'},
{
"b": {"b": 'b'}
},
[1]
];
final next = [
1,
{"a": 'a'},
{
"b": {"b": 'c'}
},
[1]
];
final result = replaceEqualDeep(prev, next);
expect(result, equals(next));
expect(result, isNot(prev));
expect(result[0], prev[0]);
expect(result[1], prev[1]);
expect((result[2] as Map)["b"]["b"], (next[2] as Map)["b"]["b"]);
expect(result[3], prev[3]);
});
test(
'Should replace different values in arrays When the next value is a subset',
() {
final prev = [
{"a": 'a'},
{"b": 'b'},
{"c": 'c'}
];
final next = [
{"a": 'a'},
{"b": 'b'}
];
final result = replaceEqualDeep(prev, next);
expect(result, equals(next));
expect(result, isNot(prev));
expect(result[0], prev[0]);
expect(result[1], prev[1]);
expect(() => result[2], throwsRangeError);
});
test(
'Should replace different values in arrays When the next value is a superset',
() {
final prev = [
{"a": 'a'},
{"b": 'b'}
];
final next = [
{"a": 'a'},
{"b": 'b'},
{"c": 'c'}
];
final result = replaceEqualDeep(prev, next);
expect(result, equals(next));
expect(result[0], equals(prev[0]));
expect(result[1], equals(prev[1]));
expect(result[2], equals(next[2]));
});
test('Should copy objects which are not arrays or objects', () {
final prev = [
{"a": 'a'},
{"b": 'b'},
{"c": 'c'},
1
];
final next = [
{"a": 'a'},
Map(),
{"c": 'c'},
2
];
final result = replaceEqualDeep(prev, next);
expect(result[0], equals(prev[0]));
expect(result[1], equals(next[1]));
expect(result[2], equals(prev[2]));
expect(result[3], equals(next[3]));
});
test('Should support equal objects which are not arrays or objects', () {
final map = new Map();
final prev = [
map,
[1]
];
final next = [
map,
[1]
];
final result = replaceEqualDeep(prev, next);
expect(result, equals(prev));
});
test('Should support non equal objects which are not arrays or objects',
() {
final map1 = new Map();
final map2 = new Map();
final prev = [
map1,
[1]
];
final next = [
map2,
[1]
];
final result = replaceEqualDeep(prev, next);
expect(result[0], equals(next[0]));
expect(result[1], equals(prev[1]));
});
test('Should replace all parent objects if some nested value changes',
() {
final prev = {
"todo": {
"id": '1',
"meta": {"createdAt": 0},
"state": {"done": false},
},
"otherTodo": {
"id": '2',
"meta": {"createdAt": 0},
"state": {"done": true},
},
};
final next = {
"todo": {
"id": '1',
"meta": {"createdAt": 0},
"state": {"done": true},
},
"otherTodo": {
"id": '2',
"meta": {"createdAt": 0},
"state": {"done": true},
},
};
final result = replaceEqualDeep(prev, next);
expect(result, equals(next));
expect(result["todo"] == prev["todo"], isFalse);
expect(result["todo"] == next["todo"], isFalse);
expect(result["todo"]["id"], equals((next["todo"] as Map)["id"]));
expect(result["todo"]["meta"], equals((prev["todo"] as Map)["meta"]));
expect(
result["todo"]["state"],
equals((next["todo"] as Map)["state"]),
);
expect(
result["todo"]["state"]["done"],
(next["todo"] as Map)["state"]["done"],
);
expect(result["otherTodo"], prev["otherTodo"]);
});
test('Should replace all parent arrays if some nested value changes', () {
final Map<String, List<Map>> prev = {
"todos": [
{
"id": '1',
"meta": {"createdAt": 0},
"state": {"done": false}
},
{
"id": '2',
"meta": {"createdAt": 0},
"state": {"done": true}
},
],
};
final Map<String, List<Map>> next = {
"todos": [
{
"id": '1',
"meta": {"createdAt": 0},
"state": {"done": true}
},
{
"id": '2',
"meta": {"createdAt": 0},
"state": {"done": true}
},
],
};
final result = replaceEqualDeep(prev, next);
expect(result, equals(next));
expect(result["todos"][0], isNot(equals(prev["todos"]?.first)));
expect(
result["todos"][0]?["id"],
equals(next["todos"]?.first["id"]),
);
expect(
result["todos"][0]?["meta"],
equals(prev["todos"]?.first["meta"]),
);
expect(result["todos"][0]?["state"]["done"],
next["todos"]?.first["state"]["done"]);
expect(result["todos"][1], equals(prev["todos"]?[1]));
});
});
group(
'matchMutation',
() => {
// test('should return false if mutationKey options is undefined', () => {
// const filters = { mutationKey: 'key1' };
// const queryClient = new QueryClient();
// const mutation = new Mutation({
// mutationId: 1,
// mutationCache: queryClient.getMutationCache(),
// options: {},
// })
// expect(matchMutation(filters, mutation)).toBeFalsy()
// })
});
});
}