feat: clean old junk

This commit is contained in:
Kingkor Roy Tirtho
2023-02-10 19:08:30 +06:00
parent 0af8190cf8
commit f632fe8403
383 changed files with 0 additions and 21486 deletions
@@ -1,20 +0,0 @@
import 'package:connectivity_plus/connectivity_plus.dart';
final _alwaysOnlineConnectivity = AlwaysOnlineConnectivity._();
/// make this singleton
class AlwaysOnlineConnectivity implements Connectivity {
AlwaysOnlineConnectivity._();
factory AlwaysOnlineConnectivity() {
return _alwaysOnlineConnectivity;
}
@override
Future<ConnectivityResult> checkConnectivity() {
return Future.value(ConnectivityResult.ethernet);
}
@override
Stream<ConnectivityResult> get onConnectivityChanged => Stream.empty();
}
@@ -1,86 +0,0 @@
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:fl_query/src/utils.dart';
import 'package:flutter/widgets.dart';
abstract class BaseOperation<Data, Error> extends ChangeNotifier {
/// The number of times the query should refetch in the time of error
/// before giving up
final int retries;
final Duration retryDelay;
// got from global options
@protected
Duration cacheTime;
Connectivity _connectivity;
// all properties
Data? data;
Error? error;
/// total count of how many times the query retried to get a successful
/// result
int retryAttempts = 0;
DateTime updatedAt;
bool fetched = false;
/// used for keeping track of query activity. If the are no mounts &
/// the passed cached time is over than the query is removed from
/// storage/cache
Set<ValueKey<String>> _mounts = {};
BaseOperation({
required this.cacheTime,
required this.retries,
required this.retryDelay,
this.data,
Connectivity? connectivity,
}) : updatedAt = DateTime.now(),
_connectivity = connectivity ?? Connectivity();
void mount(ValueKey<String> uKey) {
_mounts.add(uKey);
}
void unmount(ValueKey<String> uKey) {
if (_mounts.length == 1) {
Future.delayed(cacheTime, () {
_mounts.remove(uKey);
// for letting know QueryBowl that this one's time has come for
// getting crushed
notifyListeners();
});
} else {
_mounts.remove(uKey);
}
}
Set<ValueKey<String>> get mounts => _mounts;
/// checks if the application is connected to internet in any mean
///
/// It's true when any one this is connected -
/// - ethernet
/// - mobile
/// - wifi
///
/// Deprecated: Use [isNetworkOnline] instead
@deprecated
Future<bool> isInternetConnected() async {
return isNetworkOnline;
}
/// checks if the application is connected to internet in any mean
///
/// It's true when any one this is connected -
/// - ethernet
/// - mobile
/// - wifi
Future<bool> get isNetworkOnline =>
_connectivity.checkConnectivity().then((v) => isConnectedToInternet(v));
bool get isInactive => mounts.isEmpty;
bool get hasData => data != null;
bool get hasError => error != null;
}
-410
View File
@@ -1,410 +0,0 @@
import 'dart:async';
import 'package:fl_query/fl_query.dart';
import 'package:fl_query/src/base_operation.dart';
import 'package:fl_query/src/mixins/autocast.dart';
import 'package:flutter/widgets.dart';
import 'package:hive/hive.dart';
abstract class BaseQuery<T extends Object, Outside, Error>
extends BaseOperation<T, Error> with AutoCast {
// all params
final String queryKey;
bool? refetchOnMount;
bool? refetchOnReconnect;
final T? _initialData;
// got from global options
Duration _staleTime;
/// total count of how many times the query retried to get a successful
/// result
int refetchCount = 0;
bool enabled;
QueryStatus status;
@protected
final Set onDataListeners = Set();
@protected
final Set onErrorListeners = Set();
// externalData will always be passed to the task Callback
// it will change based on the presence of QueryBuilder
Outside _externalData;
Outside? _prevUsedExternalData;
Duration? refetchInterval;
Timer? _refetchIntervalTimer;
bool? refetchOnApplicationResume;
bool? refetchOnWindowFocus;
T? _previousData;
BaseQuery({
required this.queryKey,
required Duration staleTime,
required super.cacheTime,
required Outside externalData,
required super.retries,
required super.retryDelay,
required this.status,
super.connectivity,
this.refetchOnMount,
this.refetchOnReconnect,
this.refetchInterval,
this.refetchOnApplicationResume,
this.refetchOnWindowFocus,
this.enabled = true,
T? previousData,
T? initialData,
onData,
onError,
}) : _staleTime = staleTime,
_initialData = initialData,
_externalData = externalData,
_previousData = previousData,
super(data: previousData ?? initialData) {
if (onData != null) onDataListeners.add(onData);
if (onError != null) onErrorListeners.add(onError);
if (refetchInterval != null && refetchInterval != Duration.zero) {
_refetchIntervalTimer = createRefetchTimer();
}
if (canCacheToDisk) {
loadFromDisk();
}
}
// all getters & setters
Outside get externalData => _externalData;
Outside? get prevUsedExternalData => _prevUsedExternalData;
@protected
Timer createRefetchTimer();
@protected
Future<void> loadFromDisk() async {
final box = await Hive.lazyBox<String>(kFlQueryBoxKey);
final rawData = await box.get(queryKey);
if (rawData == null) return;
data = deserialize(rawData);
if (!isLoading && !isRefetching) {
status = QueryStatus.cached;
}
updatedAt = DateTime.now();
await notifyDataListeners();
notifyListeners();
}
Future<void> saveToDisk({bool delete = false}) async {
if (!canCacheToDisk) return;
if (!hasData) {
if (delete) {
final box = await Hive.lazyBox<String>(kFlQueryBoxKey);
await box.delete(queryKey);
}
return;
}
final box = await Hive.lazyBox<String>(kFlQueryBoxKey);
await box.put(queryKey, serialize(data!)!);
}
/// Calls the task function & doesn't check if there's already
/// cached data available
@protected
Future<void> execute() async {
try {
retryAttempts = 0;
await setData();
await saveToDisk();
_prevUsedExternalData = _externalData;
updatedAt = DateTime.now();
status = QueryStatus.success;
await notifyDataListeners();
notifyListeners();
} catch (e) {
if (retries == 0) {
status = QueryStatus.error;
setError(e);
await notifyErrorListeners();
notifyListeners();
} else {
// retrying for retry count if failed for the first time
while (retryAttempts <= retries) {
await Future.delayed(retryDelay);
try {
await setData();
await saveToDisk();
_prevUsedExternalData = _externalData;
status = QueryStatus.success;
await notifyDataListeners();
notifyListeners();
break;
} catch (e) {
if (retryAttempts == retries) {
status = QueryStatus.error;
setError(e);
await notifyErrorListeners();
notifyListeners();
break;
}
retryAttempts++;
}
}
}
}
}
void addDataListener(listener) {
onDataListeners.add(listener);
}
void addErrorListener(listener) {
onErrorListeners.add(listener);
}
void removeDataListener(listener) {
onDataListeners.remove(listener);
}
void removeErrorListener(listener) {
onErrorListeners.remove(listener);
}
/// fetches data or runs the provided task initially
///
/// Once [data] is available it won't run the [task] ever again
/// and will only return the available data
///
/// If a [fetch] is already running in the background it'll just return
/// the current available [data] (which can be nul if no [initialPage]
/// was provided) instead of running the task to prevent race conditions
Future<T?> fetch() async {
if (!enabled) return null;
if (isCachedData) {
status = QueryStatus.loading;
await execute().then((_) {
fetched = true;
});
notifyListeners();
return data;
}
final x = hasData && !isPreviousData;
var isOnline = await isNetworkOnline;
/// if isLoading/isRefetching is true that means its already fetching/
/// refetching. So [_execute] again can create a race condition
if (isLoading || isRefetching || !isOnline || (hasData && !isPreviousData))
return data;
status = QueryStatus.loading;
notifyListeners();
return execute().then((_) {
fetched = true;
return data;
});
}
/// refetches a valid or invalid [Query]
///
/// When called before calling [fetch] in a [Query] it'll
/// automatically run [fetch]
///
/// But if it's used to fetch the first data of a non-enabled [Query]
/// aka `LazyQuery`, it'll execute the task & will set the status
/// `enabled=true`
///
/// If a [refetch] is already running in the background it'll just return
/// the current available [data] instead of running the task to prevent
/// race conditions
Future<T?> refetch() async {
/// if isLoading/isRefetching is true that means its already fetching/
/// refetching. So [_execute] again can create a race condition
if (isRefetching || isLoading || !(await isNetworkOnline)) return data;
if (enabled && !fetched) return await fetch();
status = QueryStatus.refetching;
refetchCount++;
// disabling the lazy query bound when query was actually called
if (!enabled) enabled = true;
notifyListeners();
return await execute().then((_) => data);
}
@protected
String? serialize(T data);
@protected
T? deserialize(String rawData);
@protected
bool get canCacheToDisk;
@protected
FutureOr<void> setData();
@protected
void setError(dynamic);
@protected
FutureOr<void> notifyDataListeners() async {
for (final onData in onDataListeners) {
await onData(data);
}
}
@protected
FutureOr<void> notifyErrorListeners() async {
for (final onError in onErrorListeners) {
await onError(error);
}
}
/// Sets the [externalData] from outside of the query
///
/// Remember, it's for the very instance of [Query]
/// So this won't persist through later UI/[Query] updates
void setExternalData(Outside externalData) {
_prevUsedExternalData = _externalData;
_externalData = externalData;
}
/// Resets the query
///
/// The values of internal state of the query are reset to the
/// initial ones
void reset() {
refetchCount = 0;
data = _previousData ?? _initialData;
saveToDisk(delete: true);
error = null;
fetched = false;
status = QueryStatus.idle;
retryAttempts = 0;
onDataListeners.clear();
onErrorListeners.clear();
mounts.clear();
}
@override
void dispose() async {
await Hive.openLazyBox(kFlQueryBoxKey).then((box) => box.delete(queryKey));
super.dispose();
}
/// Update configurations of the query
/// after already creating the Query instance
///
/// Remember, it's just for the single query instance
/// In the next UI update/render the options will get reset
/// to the default ones defined in the [QueryJob] or [QueryBowlScope]
void updateDefaultOptions({
Duration? refetchInterval,
Duration? staleTime,
Duration? cacheTime,
bool? refetchOnMount,
bool? refetchOnReconnect,
bool? refetchOnApplicationResume,
bool? refetchOnWindowFocus,
}) {
bool updated = false;
if (this.refetchInterval == null &&
refetchInterval != null &&
refetchInterval != Duration.zero) {
this.refetchInterval = refetchInterval;
_refetchIntervalTimer?.cancel();
_refetchIntervalTimer = createRefetchTimer();
updated = true;
}
if (this.cacheTime == Duration(minutes: 5) && cacheTime != null) {
this.cacheTime = cacheTime;
updated = true;
}
if (this._staleTime == const Duration(milliseconds: 500) &&
staleTime != null) {
this._staleTime = staleTime;
updated = true;
}
if (this.refetchOnMount == null && refetchOnMount != null) {
this.refetchOnMount = refetchOnMount;
updated = true;
}
if (this.refetchOnReconnect == null && refetchOnReconnect != null) {
this.refetchOnReconnect = refetchOnReconnect;
updated = true;
}
if (this.refetchOnApplicationResume == null &&
refetchOnApplicationResume != null) {
this.refetchOnApplicationResume = refetchOnApplicationResume;
updated = true;
}
if (this.refetchOnWindowFocus == null && refetchOnWindowFocus != null) {
this.refetchOnWindowFocus = refetchOnWindowFocus;
updated = true;
}
if (updated) notifyListeners();
}
/// can be used to update the data manually. Can be useful when used
/// together with mutations to perform optimistic updates or manual data
/// updates
/// For updating particular queries after a mutation using the
/// `QueryBowl.refetchQueries` is more appropriate. But this one can be
/// used when only 1 query needs get updated
///
/// Every time a new instance of data should be returned because of
/// immutability
void setQueryData(QueryUpdateFunction<T> updateFn) async {
final newData = await updateFn(data);
if (data == newData) return;
data = newData;
await saveToDisk();
status = QueryStatus.success;
notifyListeners();
}
/// invalidates the query
///
/// Forcefully makes the query stale & expired which results in a refetch
/// when met conditions
void invalidate() {
/// subtracting [staleTime] from [updatedAt] as staleTime=Duration.zero
/// indicates the query must never become stale but subtracting the
/// [staleTime] will always revert the updatedAt time to the default
/// time whenever isStale is called
updatedAt = updatedAt.subtract(_staleTime);
notifyListeners();
}
bool get isStale {
/// when [_staleTime] is [Duration.zero], the query will always be
/// stale & will never refetch in the background. But can be inactive
/// if [mounts.length] become zero
if (_staleTime == Duration.zero) return false;
// when [DateTime.now()] is after [update_at + stale_time] it means
// the data has become stale
return DateTime.now().isAfter(updatedAt.add(_staleTime));
}
bool get isError => status == QueryStatus.error;
bool get isIdle => status == QueryStatus.idle;
bool get isLoading => status == QueryStatus.loading;
bool get isRefetching => status == QueryStatus.refetching;
bool get isSuccess => status == QueryStatus.success;
bool get isCachedData => status == QueryStatus.cached;
bool get isPreviousData {
return _previousData != null ? _previousData == data : false;
}
String get debugLabel;
@override
String toString() {
return debugLabel;
}
operator ==(other);
}
@@ -1,327 +0,0 @@
import 'dart:async';
import 'dart:convert';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:fl_query/fl_query.dart';
import 'package:fl_query/src/base_query.dart';
import 'package:flutter/cupertino.dart';
import 'package:queue/queue.dart';
typedef InfiniteQueryTaskFunction<T extends Object, Outside,
PageParam extends Object>
= FutureOr<T> Function(
String queryKey,
PageParam pageParam,
Outside externalData,
);
typedef InfiniteQueryListeners<T, PageParam extends Object> = FutureOr<void>
Function(T page, PageParam pageParam, List<T?> pages);
typedef InfiniteQueryPageParamFunction<T extends Object,
PageParam extends Object>
= FutureOr<PageParam?> Function(T lastPage, PageParam lastParam);
class InfiniteQuery<T extends Object, Outside, PageParam extends Object>
extends BaseQuery<Map<PageParam, T?>, Outside, Map<PageParam, dynamic>> {
InfiniteQueryTaskFunction<T, Outside, PageParam> task;
InfiniteQueryPageParamFunction<T, PageParam>? getNextPageParam;
InfiniteQueryPageParamFunction<T, PageParam>? getPreviousPageParam;
bool _hasNextPage = true;
bool _hasPreviousPage = true;
bool _isFetchingNextPage = false;
bool _isFetchingPreviousPage = false;
bool get isFetchingNextPage => _isFetchingNextPage;
bool get isFetchingPreviousPage => _isFetchingPreviousPage;
bool get hasNextPage => _hasNextPage;
bool get hasPreviousPage => _hasPreviousPage;
PageParam _currentParam;
final Set<InfiniteQueryListeners<T, PageParam>> onDataListeners = Set();
final Set<InfiniteQueryListeners<dynamic, PageParam>> onErrorListeners =
Set();
final SerializeFunction<T>? _serializePage;
final DeserializeFunction<T>? _deserializePage;
final SerializeFunction<PageParam>? serializePageParam;
final DeserializeFunction<PageParam>? deserializePageParam;
InfiniteQuery({
required super.queryKey,
required this.task,
required super.staleTime,
required super.cacheTime,
required super.externalData,
required super.retries,
required super.retryDelay,
required super.status,
required PageParam initialParam,
super.refetchOnMount,
super.refetchOnReconnect,
super.refetchInterval,
super.enabled,
super.previousData,
super.connectivity,
super.refetchOnApplicationResume,
DeserializeFunction<T>? deserialize,
SerializeFunction<T>? serialize,
InfiniteQueryListeners<T, PageParam>? super.onData,
InfiniteQueryListeners<dynamic, PageParam>? super.onError,
required T? initialPage,
this.getNextPageParam,
this.getPreviousPageParam,
this.serializePageParam,
this.deserializePageParam,
}) : _currentParam = initialParam,
_serializePage = serialize,
_deserializePage = deserialize,
super(
initialData: {initialParam: initialPage},
);
InfiniteQuery.fromOptions(
InfiniteQueryJob<T, Outside, PageParam> options, {
required Outside externalData,
InfiniteQueryListeners<T, PageParam>? onData,
InfiniteQueryListeners<dynamic, PageParam>? onError,
}) : task = options.task,
_currentParam = options.initialParam,
getNextPageParam = options.getNextPageParam,
getPreviousPageParam = options.getPreviousPageParam,
_serializePage = options.serialize,
_deserializePage = options.deserialize,
serializePageParam = options.serializePageParam,
deserializePageParam = options.deserializePageParam,
super(
cacheTime: options.cacheTime ?? const Duration(minutes: 5),
retries: options.retries ?? 3,
retryDelay: options.retryDelay ?? const Duration(milliseconds: 200),
externalData: externalData,
enabled: options.enabled ?? true,
staleTime: options.staleTime ?? const Duration(milliseconds: 500),
refetchInterval: options.refetchInterval,
refetchOnMount: options.refetchOnMount,
refetchOnReconnect: options.refetchOnReconnect,
status: QueryStatus.idle,
connectivity: options.connectivity ?? Connectivity(),
queryKey: options.queryKey,
initialData: {options.initialParam: options.initialPage},
refetchOnApplicationResume: options.refetchOnApplicationResume,
onData: onData,
onError: onError,
);
List<PageParam> get pageParams => data?.keys.toList() ?? [];
List<dynamic> get errors => error?.values.toList() ?? [];
List<T?> get pages => data?.values.toList() ?? [];
@override
@protected
Timer createRefetchTimer() {
return Timer.periodic(
refetchInterval!,
(_) async {
// only refetch if its connected to the internet or refetch will
// always result in error while there's no internet
if (isStale && await isNetworkOnline) await refetchPages();
},
);
}
Future<T?> fetchNextPage([
InfiniteQueryPageParamFunction<T, PageParam>? getNextPageParam,
]) async {
try {
if (isFetchingNextPage ||
isFetchingPreviousPage ||
isLoading ||
isRefetching) return null;
final page = data?[_currentParam];
if (data == null || page == null) await execute();
_isFetchingNextPage = true;
_isFetchingPreviousPage = false;
final nextParam = page != null
? await (getNextPageParam ?? this.getNextPageParam)?.call(
page,
_currentParam,
)
: null;
if (nextParam == null) {
_hasNextPage = false;
notifyListeners();
return null;
} else {
_hasNextPage = true;
_currentParam = nextParam;
return await fetch().then((_) => data?[_currentParam]);
}
} finally {
_isFetchingNextPage = false;
notifyListeners();
}
}
Future<T?> fetchPreviousPage([
InfiniteQueryPageParamFunction<T, PageParam>? getPreviousPageParam,
]) async {
try {
if (isFetchingNextPage ||
isFetchingPreviousPage ||
isLoading ||
isRefetching) return null;
_isFetchingPreviousPage = true;
_isFetchingNextPage = false;
notifyListeners();
final page = data?[_currentParam];
if (page == null) await execute();
final prevParam = page != null
? await (getPreviousPageParam ?? this.getPreviousPageParam)?.call(
page,
_currentParam,
)
: null;
if (prevParam == null) {
_hasPreviousPage = false;
notifyListeners();
return null;
}
_hasPreviousPage = true;
_currentParam = prevParam;
return await fetch().then((_) => data?[_currentParam]);
} catch (e) {
print("[InfiniteQuery.fetchPreviousPage]: $e");
rethrow;
} finally {
_isFetchingPreviousPage = false;
notifyListeners();
}
}
Future<List<T>> refetchPages([
bool Function(T? page, PageParam pageParam, List<T?> allPages)? selector,
]) async {
if (isFetchingNextPage ||
isFetchingPreviousPage ||
isLoading ||
isRefetching) return [];
final refetchedPages = <T>[];
final queue = Queue();
for (final entry in data?.entries.toList() ?? <MapEntry<PageParam, T?>>[]) {
final page = entry.value;
final selected = selector?.call(page, entry.key, pages) ?? true;
if (!selected) continue;
_currentParam = entry.key;
queue.add<void>(
() async {
final s = await refetch();
if (s?[_currentParam] != null) {
refetchedPages.add(s![_currentParam]!);
}
},
);
}
await queue.onComplete;
return refetchedPages;
}
@override
String get debugLabel => "InfiniteQuery($queryKey)";
@override
void mount(ValueKey<String> uKey) {
super.mount(uKey);
/// refetching on mount if it's set to true
/// also checking if the is stale or not
/// no need to refetch a valid query for no reason
if (refetchOnMount == true && isStale) {
isNetworkOnline.then((isConnected) async {
if (isConnected) await refetchPages();
});
}
}
@override
@protected
void setData() async {
if (data == null) data = Map();
data?[_currentParam] = await task(
queryKey,
_currentParam,
externalData,
);
}
@override
@protected
void setError(specError) {
if (error is! Map) error = Map();
error?[_currentParam] = specError;
}
@override
@protected
FutureOr<void> notifyDataListeners() async {
for (var onData in onDataListeners) {
if (data?[_currentParam] == null) continue;
onData.call(data![_currentParam]!, _currentParam, pages);
}
}
@override
@protected
FutureOr<void> notifyErrorListeners() async {
for (var onError in onErrorListeners) {
onError.call(error?[_currentParam], _currentParam, errors);
}
}
@override
bool operator ==(other) {
return (other is InfiniteQuery<T, Outside, PageParam> &&
other.queryKey == queryKey) ||
identical(other, this);
}
@override
bool get hasData => data?[_currentParam] != null;
@override
bool get canCacheToDisk =>
_serializePage != null &&
_deserializePage != null &&
serializePageParam != null &&
deserializePageParam != null;
@override
deserialize(String rawData) {
if (deserializePageParam == null || _deserializePage == null) return null;
return Map.from(jsonDecode(rawData)).cast<String, String>().map(
(key, value) {
return MapEntry(deserializePageParam!(key), _deserializePage!(value));
},
);
}
@override
serialize(data) {
if (serializePageParam == null || _serializePage == null) return null;
data.removeWhere((key, value) => value == null);
return jsonEncode(data.map(
(key, value) {
return MapEntry(
serializePageParam!(key),
_serializePage!(value!),
);
},
));
}
}
@@ -1,160 +0,0 @@
// ignore_for_file: invalid_use_of_protected_member
import 'package:fl_query/src/infinite_query.dart';
import 'package:fl_query/src/models/infinite_query_job.dart';
import 'package:fl_query/src/query_bowl.dart';
import 'package:fl_query/src/utils.dart';
import 'package:flutter/widgets.dart';
class InfiniteQueryBuilder<T extends Object, Outside, PageParam extends Object>
extends StatefulWidget {
final Function(
BuildContext context,
InfiniteQuery<T, Outside, PageParam> query,
) builder;
final InfiniteQueryJob<T, Outside, PageParam> job;
final Outside externalData;
final InfiniteQueryListeners<T, PageParam>? onData;
final InfiniteQueryListeners<dynamic, PageParam>? onError;
InfiniteQueryBuilder({
required this.job,
required this.builder,
required this.externalData,
this.onData,
this.onError,
Key? key,
}) : super(key: key);
@override
State<InfiniteQueryBuilder<T, Outside, PageParam>> createState() =>
_InfiniteQueryBuilderState<T, Outside, PageParam>();
}
class _InfiniteQueryBuilderState<T extends Object, Outside,
PageParam extends Object>
extends State<InfiniteQueryBuilder<T, Outside, PageParam>> {
InfiniteQuery<T, Outside, PageParam>? infiniteQuery;
late QueryBowl queryBowl;
late final ValueKey<String> uKey;
@override
void initState() {
super.initState();
uKey = ValueKey<String>(uuid.v4());
WidgetsBinding.instance.addPostFrameCallback((_) {
init();
QueryBowl.of(context).onInfiniteQueriesUpdate<T, Outside, PageParam>(
(infiniteQuery) async {
if (infiniteQuery.queryKey != widget.job.queryKey) return;
if (mounted)
setState(() {
this.infiniteQuery = infiniteQuery;
});
if (infiniteQuery.isCachedData && !infiniteQuery.fetched)
await infiniteQuery.refetchPages();
},
);
});
}
void init([T? previousData]) async {
final bowl = QueryBowl.of(context);
final prevInfiniteQuery =
bowl.getInfiniteQuery<T, Outside, PageParam>(widget.job.queryKey);
infiniteQuery = bowl.addInfiniteQuery<T, Outside, PageParam>(
widget.job,
externalData: widget.externalData,
key: uKey,
);
final hasExternalDataChanged = prevInfiniteQuery != null
? !isShallowEqual(
prevInfiniteQuery.externalData,
prevInfiniteQuery.prevUsedExternalData,
)
: !isShallowEqual(
infiniteQuery!.externalData,
infiniteQuery!.prevUsedExternalData,
);
if (infiniteQuery!.fetched && hasExternalDataChanged) {
await infiniteQuery!.refetchPages();
} else if (!infiniteQuery!.fetched) {
await infiniteQuery!.fetch();
}
}
@override
void didUpdateWidget(covariant oldWidget) {
final hasOnErrorChanged =
oldWidget.onError != widget.onError && oldWidget.onError != null;
final hasOnDataChanged =
oldWidget.onData != widget.onData && oldWidget.onData != null;
// re-init the query-builder when new queryJob is appended
if (oldWidget.job.queryKey != widget.job.queryKey) {
_infiniteQueryDispose();
init();
/// setting the new query's initial data as prev query's data
/// when [job.keepPreviousData] is true and both are dynamic
// if (oldWidget.job.isDynamic &&
// widget.job.isDynamic &&
// oldWidget.job.keepPreviousData == true &&
// widget.job.keepPreviousData == true) {
// init(infiniteQuery?.pages);
// } else {
// init();
// }
} else if (!isShallowEqual(oldWidget.externalData, widget.externalData)) {
if (widget.job.refetchOnExternalDataChange ??
queryBowl.refetchOnExternalDataChange) {
QueryBowl.of(context).addInfiniteQuery(
widget.job,
externalData: widget.externalData,
key: uKey,
onData: widget.onData,
onError: widget.onError,
)..refetchPages();
} else {
QueryBowl.of(context)
.getQuery(widget.job.queryKey)
?.setExternalData(widget.externalData);
}
if (hasOnDataChanged)
infiniteQuery?.removeDataListener(oldWidget.onData!);
if (hasOnErrorChanged)
infiniteQuery?.removeErrorListener(oldWidget.onError!);
} else {
if (hasOnDataChanged) {
infiniteQuery?.removeDataListener(oldWidget.onData!);
if (widget.onData != null)
infiniteQuery?.addDataListener(widget.onData!);
}
if (hasOnErrorChanged) {
infiniteQuery?.removeErrorListener(oldWidget.onError!);
if (widget.onError != null)
infiniteQuery?.addErrorListener(widget.onError!);
}
}
super.didUpdateWidget(oldWidget);
}
_infiniteQueryDispose() {
infiniteQuery?.unmount(uKey);
if (widget.onData != null)
infiniteQuery?.removeDataListener(widget.onData!);
if (widget.onError != null)
infiniteQuery?.removeErrorListener(widget.onError!);
}
@override
Widget build(BuildContext context) {
queryBowl = QueryBowl.of(context);
final latestInfiniteQuery = queryBowl
.getInfiniteQuery<T, Outside, PageParam>(widget.job.queryKey) ??
infiniteQuery;
if (latestInfiniteQuery == null) return Container();
return widget.builder(context, latestInfiniteQuery);
}
}
@@ -1,3 +0,0 @@
mixin AutoCast {
A? cast<A>() => this is A ? this as A : null;
}
@@ -1,9 +0,0 @@
class InfiniteQueryData<T extends Object> {
final Set<T> pages;
final Set<String> pageParams;
InfiniteQueryData({
required this.pages,
required this.pageParams,
});
}
@@ -1,138 +0,0 @@
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:fl_query/src/infinite_query.dart';
import 'package:fl_query/src/models/query_job.dart';
import 'package:flutter/widgets.dart';
class InfiniteQueryJob<T extends Object, Outside, PageParam extends Object> {
// all params
String _queryKey;
InfiniteQueryTaskFunction<T, Outside, PageParam> task;
SerializeFunction<T>? serialize;
DeserializeFunction<T>? deserialize;
SerializeFunction<PageParam>? serializePageParam;
DeserializeFunction<PageParam>? deserializePageParam;
final int? retries;
final Duration? retryDelay;
T? initialPage;
PageParam initialParam;
/// If set to false then the initial fetch will not be called & to
/// start the process the user has to call the refetch first
final bool? enabled;
// got from global options
final bool? refetchOnMount;
final bool? refetchOnReconnect;
final bool? refetchOnExternalDataChange;
final bool? refetchOnApplicationResume;
final bool? refetchOnWindowFocus;
final Duration? staleTime;
final Duration? cacheTime;
final Duration? refetchInterval;
final Connectivity? connectivity;
final InfiniteQueryPageParamFunction<T, PageParam> getNextPageParam;
final InfiniteQueryPageParamFunction<T, PageParam> getPreviousPageParam;
@protected
bool isDynamic = false;
InfiniteQueryJob({
required String queryKey,
required this.task,
required this.initialParam,
required this.getNextPageParam,
required this.getPreviousPageParam,
this.retries,
this.retryDelay,
this.initialPage,
this.staleTime,
this.cacheTime,
this.enabled,
this.refetchInterval,
this.refetchOnMount,
this.refetchOnReconnect,
this.refetchOnExternalDataChange,
this.refetchOnApplicationResume,
this.refetchOnWindowFocus,
this.connectivity,
this.deserialize,
this.serialize,
this.serializePageParam,
this.deserializePageParam,
}) : assert(
serialize == null &&
deserialize == null &&
serializePageParam == null &&
deserializePageParam == null ||
(serialize != null &&
deserialize != null &&
serializePageParam != null &&
deserializePageParam != null &&
enabled != false),
"All or none of the serialize, deserialize, serializePageParam & deserializePageParam function must be provided. And `enabled` must be true if all of them are provided.",
),
_queryKey = queryKey;
String get queryKey => _queryKey;
static InfiniteQueryJob<T, Outside, PageParam> Function(String queryKey)
withVariableKey<T extends Object, Outside, PageParam extends Object>({
required InfiniteQueryTaskFunction<T, Outside, PageParam> task,
required InfiniteQueryPageParamFunction<T, PageParam> getNextPageParam,
required InfiniteQueryPageParamFunction<T, PageParam> getPreviousPageParam,
required PageParam initialParam,
/// a extra key joined with queryKey by a '#'
///
/// useful for matching a group query
String? preQueryKey,
int? retries,
Duration? retryDelay,
T? initialPage,
Duration? staleTime,
Duration? cacheTime,
bool? enabled,
Duration? refetchInterval,
bool? refetchOnMount,
bool? refetchOnReconnect,
bool? refetchOnExternalDataChange,
bool? refetchOnApplicationResume,
bool? refetchOnWindowFocus,
Connectivity? connectivity,
SerializeFunction<T>? serialize,
DeserializeFunction<T>? deserialize,
SerializeFunction<PageParam>? serializePageParam,
DeserializeFunction<PageParam>? deserializePageParam,
}) {
return (String queryKey) {
if (preQueryKey != null) queryKey = "$preQueryKey#$queryKey";
final query = InfiniteQueryJob<T, Outside, PageParam>(
queryKey: queryKey,
task: task,
getNextPageParam: getNextPageParam,
getPreviousPageParam: getPreviousPageParam,
retries: retries,
retryDelay: retryDelay,
initialPage: initialPage,
staleTime: staleTime,
cacheTime: cacheTime,
enabled: enabled,
refetchInterval: refetchInterval,
refetchOnMount: refetchOnMount,
refetchOnReconnect: refetchOnReconnect,
refetchOnExternalDataChange: refetchOnExternalDataChange,
refetchOnApplicationResume: refetchOnApplicationResume,
refetchOnWindowFocus: refetchOnWindowFocus,
connectivity: connectivity,
initialParam: initialParam,
serialize: serialize,
deserialize: deserialize,
serializePageParam: serializePageParam,
deserializePageParam: deserializePageParam,
);
query.isDynamic = true;
return query;
};
}
}
@@ -1,48 +0,0 @@
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:fl_query/fl_query.dart';
class MutationJob<T extends Object, V> {
String _mutationKey;
MutationTaskFunction<T, V> task;
final int? retries;
final Duration? retryDelay;
final Duration? cacheTime;
final Connectivity? connectivity;
MutationJob({
required String mutationKey,
required this.task,
this.connectivity,
this.retries,
this.retryDelay,
this.cacheTime,
}) : _mutationKey = mutationKey;
String get mutationKey => _mutationKey;
static MutationJob<T, V> Function(String queryKey)
withVariableKey<T extends Object, V>({
required MutationTaskFunction<T, V> task,
/// a extra key joined with mutationKey by a '#'
///
/// useful for matching a group mutation
String? preMutationKey,
int? retries,
Duration? retryDelay,
Duration? cacheTime,
Connectivity? connectivity,
}) {
return (String mutationKey) {
if (preMutationKey != null) mutationKey = "$preMutationKey#$mutationKey";
return MutationJob<T, V>(
mutationKey: mutationKey,
task: task,
retries: retries,
retryDelay: retryDelay,
cacheTime: cacheTime,
connectivity: connectivity,
);
};
}
}
@@ -1,117 +0,0 @@
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:fl_query/src/query.dart';
import 'package:flutter/widgets.dart';
typedef SerializeFunction<T extends Object> = String Function(T data);
typedef DeserializeFunction<T extends Object> = T Function(String raw);
class QueryJob<T extends Object, Outside> {
// all params
String _queryKey;
QueryTaskFunction<T, Outside> task;
final SerializeFunction<T>? serialize;
final DeserializeFunction<T>? deserialize;
final int? retries;
final Duration? retryDelay;
T? initialData;
/// If set to false then the initial fetch will not be called & to
/// start the process the user has to call the refetch first
final bool? enabled;
// got from global options
final bool? refetchOnMount;
final bool? refetchOnReconnect;
final bool? refetchOnExternalDataChange;
final bool? refetchOnApplicationResume;
final bool? refetchOnWindowFocus;
final bool? keepPreviousData;
final Duration? staleTime;
final Duration? cacheTime;
final Duration? refetchInterval;
final Connectivity? connectivity;
@protected
bool isDynamic = false;
QueryJob({
required String queryKey,
required this.task,
this.retries,
this.retryDelay,
this.initialData,
this.staleTime,
this.cacheTime,
this.enabled,
this.refetchInterval,
this.refetchOnMount,
this.refetchOnReconnect,
this.refetchOnExternalDataChange,
this.connectivity,
this.keepPreviousData,
this.refetchOnApplicationResume,
this.refetchOnWindowFocus,
this.deserialize,
this.serialize,
}) : assert(
serialize == null && deserialize == null ||
(serialize != null && deserialize != null && enabled != false),
"Both or none of the serialize and deserialize function must be provided and enabled must be true if you want to use disk caching",
),
_queryKey = queryKey;
String get queryKey => _queryKey;
static QueryJob<T, Outside> Function(String queryKey)
withVariableKey<T extends Object, Outside>({
required QueryTaskFunction<T, Outside> task,
/// a extra key joined with queryKey by a '#'
///
/// useful for matching a group query
String? preQueryKey,
int? retries,
Duration? retryDelay,
T? initialData,
Duration? staleTime,
Duration? cacheTime,
bool? enabled,
Duration? refetchInterval,
bool? refetchOnMount,
bool? refetchOnReconnect,
bool? refetchOnExternalDataChange,
bool? refetchOnApplicationResume,
bool? refetchOnWindowFocus,
Connectivity? connectivity,
bool? keepPreviousData,
SerializeFunction<T>? serialize,
DeserializeFunction<T>? deserialize,
}) {
return (String queryKey) {
if (preQueryKey != null) queryKey = "$preQueryKey#$queryKey";
final query = QueryJob<T, Outside>(
queryKey: queryKey,
task: task,
retries: retries,
retryDelay: retryDelay,
initialData: initialData,
staleTime: staleTime,
cacheTime: cacheTime,
enabled: enabled,
refetchInterval: refetchInterval,
refetchOnMount: refetchOnMount,
refetchOnReconnect: refetchOnReconnect,
refetchOnExternalDataChange: refetchOnExternalDataChange,
connectivity: connectivity,
keepPreviousData: keepPreviousData,
refetchOnApplicationResume: refetchOnApplicationResume,
refetchOnWindowFocus: refetchOnWindowFocus,
serialize: serialize,
deserialize: deserialize,
);
query.isDynamic = true;
return query;
};
}
}
-222
View File
@@ -1,222 +0,0 @@
import 'dart:async';
import 'package:fl_query/fl_query.dart';
import 'package:fl_query/src/base_operation.dart';
import 'package:fl_query/src/mixins/autocast.dart';
import 'package:fl_query/src/models/mutation_job.dart';
import 'package:flutter/widgets.dart';
enum MutationStatus {
error,
success,
loading,
idle,
}
typedef MutationListenerReturnable<T, R> = FutureOr<R> Function(T);
typedef MutationListener<T, V> = FutureOr<void> Function(
T payload,
V variables,
dynamic realData,
);
typedef MutationTaskFunction<T, V> = FutureOr<T> Function(
String queryKey, V variables);
class Mutation<T extends Object, V> extends BaseOperation<T, dynamic>
with AutoCast {
// all params
final String mutationKey;
MutationTaskFunction<T, V> task;
MutationStatus status;
dynamic _sideEffectContext;
@protected
final Set<MutationListener<T, V>> _onDataListeners = {};
@protected
final Set<MutationListener<dynamic, V>> _onErrorListeners = {};
@protected
final Set<MutationListenerReturnable<V, dynamic>> _onMutateListeners = {};
// using late as _variables will only be used after a [mutate] or
// [mutateAsync] is executed
late V _variables;
Mutation({
required this.mutationKey,
required this.task,
required super.retries,
required super.retryDelay,
super.connectivity,
required Duration cacheTime,
MutationListener<T, V>? onData,
MutationListener<dynamic, V>? onError,
MutationListenerReturnable<V, dynamic>? onMutate,
}) : status = MutationStatus.idle,
super(cacheTime: cacheTime) {
if (onData != null) _onDataListeners.add(onData);
if (onError != null) _onErrorListeners.add(onError);
if (onMutate != null) _onMutateListeners.add(onMutate);
}
Mutation.fromOptions(
MutationJob<T, V> options, {
MutationListener<T, V>? onData,
MutationListener<dynamic, V>? onError,
MutationListenerReturnable<V, dynamic>? onMutate,
}) : mutationKey = options.mutationKey,
task = options.task,
status = MutationStatus.idle,
super(
retries: options.retries ?? 3,
retryDelay: options.retryDelay ?? const Duration(milliseconds: 200),
cacheTime: options.cacheTime ?? const Duration(minutes: 5),
connectivity: options.connectivity,
) {
if (onData != null) _onDataListeners.add(onData);
if (onError != null) _onErrorListeners.add(onError);
}
// all methods
/// Calls the task function & doesn't check if there's already
/// cached data available
Future<void> _execute(V variables) async {
try {
status = MutationStatus.loading;
notifyListeners();
retryAttempts = 0;
for (final onMutate in _onMutateListeners) {
_sideEffectContext = await onMutate(variables);
}
data = await task(mutationKey, variables);
updatedAt = DateTime.now();
status = MutationStatus.success;
for (final onData in _onDataListeners) {
onData(data!, _variables, _sideEffectContext);
}
notifyListeners();
} catch (e) {
if (retries == 0) {
status = MutationStatus.error;
error = e;
for (final onError in _onErrorListeners) {
onError(error, variables, _sideEffectContext);
}
notifyListeners();
throw e;
} else {
// retrying for retry count if failed for the first time
while (retryAttempts <= retries) {
await Future.delayed(retryDelay);
try {
for (final onMutate in _onMutateListeners) {
_sideEffectContext = onMutate(variables);
}
data = await task(mutationKey, variables);
status = MutationStatus.success;
for (final onData in _onDataListeners) {
onData(data!, variables, _sideEffectContext);
}
notifyListeners();
break;
} catch (e) {
if (retryAttempts == retries) {
status = MutationStatus.error;
error = e;
for (final onError in _onErrorListeners) {
onError(error, variables, _sideEffectContext);
}
notifyListeners();
throw e;
}
retryAttempts++;
}
}
}
}
}
void addDataListener(MutationListener<T, V> listener) {
_onDataListeners.add(listener);
}
void addErrorListener(MutationListener<dynamic, V> listener) {
_onErrorListeners.add(listener);
}
void addMutateListener(MutationListenerReturnable<V, dynamic> listener) {
_onMutateListeners.add(listener);
}
void removeDataListener(MutationListener<T, V> listener) {
_onDataListeners.remove(listener);
}
void removeErrorListener(MutationListener<dynamic, V> listener) {
_onErrorListeners.remove(listener);
}
void removeMutateListener(MutationListenerReturnable<V, dynamic> listener) {
_onMutateListeners.remove(listener);
}
void mutate(
V variables, {
MutationListener<T, V>? onData,
MutationListener<dynamic, V>? onError,
}) {
_variables = variables;
if (onData != null) _onDataListeners.add(onData);
if (onError != null) _onErrorListeners.add(onError);
try {
_execute(variables).then((_) {
_onDataListeners.remove(onData);
_onErrorListeners.remove(onError);
});
} catch (e) {
return;
}
}
Future<T?> mutateAsync(V variables) async {
_variables = variables;
return await _execute(variables).then((_) => data);
}
/// Update configurations of the mutation after already creating the
/// Mutation instance
void updateDefaultOptions({
Duration? cacheTime,
}) {
if (this.cacheTime == Duration(minutes: 5) && cacheTime != null)
this.cacheTime = cacheTime;
notifyListeners();
}
void reset() {
data = null;
retryAttempts = 0;
updatedAt = DateTime.now();
_onDataListeners.clear();
_onErrorListeners.clear();
status = MutationStatus.idle;
_onMutateListeners.clear();
_sideEffectContext = null;
}
bool get isError => status == MutationStatus.error;
bool get isIdle => status == MutationStatus.idle;
bool get isLoading => status == MutationStatus.loading;
bool get isSuccess => status == MutationStatus.success;
@override
bool operator ==(other) {
return (other is Mutation<T, V> && other.mutationKey == mutationKey) ||
identical(other, this);
}
}
@@ -1,117 +0,0 @@
// ignore_for_file: invalid_use_of_protected_member
import 'package:fl_query/src/models/mutation_job.dart';
import 'package:fl_query/src/mutation.dart';
import 'package:fl_query/src/query_bowl.dart';
import 'package:fl_query/src/utils.dart';
import 'package:flutter/widgets.dart';
class MutationBuilder<T extends Object, V> extends StatefulWidget {
final Function(BuildContext context, Mutation<T, V> mutation) builder;
final MutationJob<T, V> job;
/// Called when the query returns new data, on query
/// refetch or query gets expired
final MutationListener<T, V>? onData;
/// Called when the query returns error
final MutationListener<dynamic, V>? onError;
/// called right before the mutation is about to run
///
/// perfect scenario for doing optimistic updates
final MutationListenerReturnable<V, dynamic>? onMutate;
const MutationBuilder({
required this.job,
required this.builder,
this.onData,
this.onError,
this.onMutate,
Key? key,
}) : super(key: key);
@override
State<MutationBuilder<T, V>> createState() => _MutationBuilderState<T, V>();
}
class _MutationBuilderState<T extends Object, V>
extends State<MutationBuilder<T, V>> {
late ValueKey<String> uKey;
Mutation<T, V>? mutation;
@override
void initState() {
super.initState();
uKey = ValueKey<String>(uuid.v4());
WidgetsBinding.instance.addPostFrameCallback((_) {
init();
QueryBowl.of(context).onMutationsUpdate<T, V>(
(mutation) {
if (mutation.mutationKey != widget.job.mutationKey || !mounted)
return;
setState(() {
this.mutation = mutation;
});
},
);
});
}
void init([_]) {
final bowl = QueryBowl.of(context);
setState(() {
mutation = bowl.addMutation<T, V>(
widget.job,
onData: widget.onData,
onError: widget.onError,
onMutate: widget.onMutate,
key: uKey,
);
});
}
@override
void didUpdateWidget(covariant MutationBuilder<T, V> oldWidget) {
if (oldWidget.job.mutationKey != widget.job.mutationKey) {
_mutationDispose();
init();
} else {
if (oldWidget.onData != widget.onData && oldWidget.onData != null) {
mutation?.removeDataListener(oldWidget.onData!);
if (widget.onData != null) mutation?.addDataListener(widget.onData!);
}
if (oldWidget.onError != widget.onError && oldWidget.onError != null) {
mutation?.removeErrorListener(oldWidget.onError!);
if (widget.onError != null) mutation?.addErrorListener(widget.onError!);
}
if (oldWidget.onMutate != widget.onMutate && oldWidget.onMutate != null) {
mutation?.removeMutateListener(oldWidget.onMutate!);
if (widget.onMutate != null)
mutation?.addMutateListener(widget.onMutate!);
}
}
super.didUpdateWidget(oldWidget);
}
@override
void dispose() {
_mutationDispose();
super.dispose();
}
void _mutationDispose() {
mutation?.unmount(uKey);
if (widget.onData != null) mutation?.addDataListener(widget.onData!);
if (widget.onError != null) mutation?.addErrorListener(widget.onError!);
if (widget.onMutate != null) mutation?.addMutateListener(widget.onMutate!);
}
@override
Widget build(BuildContext context) {
if (mutation == null) return Container();
return widget.builder(context, mutation!);
}
}
-162
View File
@@ -1,162 +0,0 @@
import 'dart:async';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:fl_query/fl_query.dart';
import 'package:fl_query/src/base_query.dart';
import 'package:fl_query/src/models/query_job.dart';
import 'package:flutter/widgets.dart';
enum QueryStatus {
/// in times when an error occurs
/// will get reset to idle on refetch/retry
error,
/// when a query successfully executes
success,
/// when the query is running (not refetching)
loading,
/// when the query isn't yet fetched, re-fetched, or got reset
/// mostly when both [data] & [error] are null. Also [fetched] is false
idle,
/// when the query is refetching (rerunning)
refetching,
/// when the query data is loaded from cache
cached,
}
typedef QueryTaskFunction<T extends Object, Outside> = FutureOr<T> Function(
String queryKey,
Outside externalData,
);
typedef QueryListener<T> = FutureOr<void> Function(T);
typedef ListenerUnsubscriber = void Function();
typedef QueryUpdateFunction<T> = FutureOr<T> Function(T? oldData);
class Query<T extends Object, Outside> extends BaseQuery<T, Outside, dynamic> {
QueryTaskFunction<T, Outside> task;
final Set<QueryListener<T>> onDataListeners = Set();
final Set<QueryListener<dynamic>> onErrorListeners = Set();
final SerializeFunction<T>? _serialize;
final DeserializeFunction<T>? _deserialize;
Query({
required super.queryKey,
required this.task,
required super.staleTime,
required super.cacheTime,
required super.externalData,
required super.retries,
required super.retryDelay,
required super.status,
super.refetchOnMount,
super.refetchOnReconnect,
super.refetchInterval,
super.enabled,
super.previousData,
super.connectivity,
super.initialData,
super.refetchOnApplicationResume,
SerializeFunction<T>? serialize,
DeserializeFunction<T>? deserialize,
QueryListener<T>? super.onData,
QueryListener<dynamic>? super.onError,
}) : _deserialize = deserialize,
_serialize = serialize;
Query.fromOptions(
QueryJob<T, Outside> options, {
required Outside externalData,
T? previousData,
QueryListener<T>? onData,
QueryListener<dynamic>? onError,
}) : task = options.task,
_deserialize = options.deserialize,
_serialize = options.serialize,
super(
cacheTime: options.cacheTime ?? const Duration(minutes: 5),
retries: options.retries ?? 3,
retryDelay: options.retryDelay ?? const Duration(milliseconds: 200),
externalData: externalData,
enabled: options.enabled ?? true,
staleTime: options.staleTime ?? const Duration(milliseconds: 500),
initialData: options.initialData,
refetchInterval: options.refetchInterval,
refetchOnMount: options.refetchOnMount,
refetchOnReconnect: options.refetchOnReconnect,
status: previousData == null ? QueryStatus.idle : QueryStatus.success,
connectivity: options.connectivity ?? Connectivity(),
previousData: previousData,
queryKey: options.queryKey,
refetchOnApplicationResume: options.refetchOnApplicationResume,
onData: onData,
onError: onError,
);
@override
Timer createRefetchTimer() {
return Timer.periodic(
refetchInterval!,
(_) async {
// only refetch if its connected to the internet or refetch will
// always result in error while there's no internet
if (isStale && await isNetworkOnline) await refetch();
},
);
}
String get debugLabel => "Query($queryKey)";
@override
void mount(ValueKey<String> uKey) {
super.mount(uKey);
/// refetching on mount if it's set to true
/// also checking if the is stale or not
/// no need to refetch a valid query for no reason
if (refetchOnMount == true && isStale) {
isNetworkOnline.then((isConnected) async {
if (isConnected) await refetch();
});
}
}
@override
bool operator ==(other) {
return (other is Query<T, Outside> && other.queryKey == queryKey) ||
identical(other, this);
}
@override
FutureOr<void> setData() async {
data = await task(queryKey, externalData);
}
@override
void setError(e) {
error = e;
}
@override
deserialize(rawData) {
if (_deserialize == null) return null;
return _deserialize!(rawData);
}
@override
serialize(data) {
if (_serialize == null) return null;
return _serialize!(data);
}
@override
bool get canCacheToDisk => _serialize != null && _deserialize != null;
}
-565
View File
@@ -1,565 +0,0 @@
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:fl_query/fl_query.dart';
import 'package:fl_query/src/query_cache.dart';
import 'package:fl_query/src/utils.dart';
import 'package:flutter/cupertino.dart';
import 'package:collection/collection.dart';
import 'package:flutter/services.dart';
import 'package:hive_flutter/hive_flutter.dart';
/// The widget that holds every [Query] & [Mutation] to your
/// entire Flutter application in anywhere
/// [QueryBowl] provides an imperative way to handle all the query &
/// mutation related methods & properties.
/// Its responsible or can be used for (not recommended) creating,
/// updating & deleting queries & mutations
///
/// This must be used along with [QueryBowlScope]
///
/// ```dart
/// Widget build(BuildContext context) {
/// return QueryBowlScope(
/// bowl: QueryBowl(),
/// child: MaterialApp(/*...other stuff...*/),
/// );
/// }
/// ```
///
class QueryBowl {
/// global stale time
///
/// Makes [Query.data] stale after crossing the duration of provided
/// [staleTime]
final Duration staleTime;
// refetching options
/// refetch query when new query instance mounts
final bool refetchOnMount;
/// refetch when desktop/web app regains Focus
final bool refetchOnWindowFocus;
/// the delay to call each query when desktop/web application gets focused after
/// being unfocused. Using a delay after each refetch so refetching all the
/// queries at once won't create high CPU spikes & also wouldn't violate
/// rate-limit
///
/// Though its recommended most of the time to use but it can be turned
/// off by passing [Duration.zero]
final Duration refetchOnWindowFocusDelay;
/// refetch when application resumes from the background in mobile devices
final bool refetchOnApplicationResume;
/// the delay to call each query when app resumes from the background in
/// mobile devices. Using a delay after each refetch so refetching all the
/// queries at once won't create high CPU spikes & also wouldn't violate
/// rate-limit
///
/// Though its recommended most of the time to use but it can be turned
/// off by passing [Duration.zero]
final Duration refetchOnApplicationResumeDelay;
/// refetch when user's device reconnects to the internet after not being
/// connected before
final bool refetchOnReconnect;
/// the delay to call each query when user device reconnects to the
/// internet. Using a delay after each refetch so refetching all the
/// queries at once won't create high CPU spikes & also wouldn't violate
/// rate-limit
///
/// Though its recommended most of the time to use but it can be turned
/// off by passing [Duration.zero]
final Duration refetchOnReconnectDelay;
/// Refetch the query whenever the data passed as [externalData] to any
/// [QueryBuilder] or [useQuery] changes
///
/// If set to false than the [externalData] will get updated but there
/// won't be any query update
final bool refetchOnExternalDataChange;
/// used for periodically checking if any query got stale.
/// If none is supplied then half of the value of staleTime is used
final Duration refetchInterval;
/// The Cache that holds all the queries, mutations and infinite queries
QueryCache cache;
QueryBowl({
/// The Cache that holds all the queries, mutations and infinite
/// queries
QueryCache? cache,
this.staleTime = Duration.zero,
/// global cache time
///
/// Removes inactive queries after provided duration of [cacheTime]
Duration? cacheTime,
this.refetchInterval = Duration.zero,
this.refetchOnMount = false,
this.refetchOnReconnect = true,
this.refetchOnReconnectDelay = const Duration(milliseconds: 100),
this.refetchOnApplicationResumeDelay = const Duration(milliseconds: 100),
this.refetchOnWindowFocusDelay = const Duration(milliseconds: 100),
this.refetchOnApplicationResume = true,
this.refetchOnWindowFocus = true,
this.refetchOnExternalDataChange = false,
}) : cache = cache ?? QueryCache(cacheTime: cacheTime) {
Connectivity()
.onConnectivityChanged
.listen((ConnectivityResult result) async {
if (isConnectedToInternet(result)) {
for (final query in this.cache.queries.toList()) {
if (query.refetchOnReconnect == false || !query.enabled) continue;
await query.refetch();
await Future.delayed(refetchOnReconnectDelay);
}
for (final infiniteQuery in this.cache.infiniteQueries.toList()) {
if (infiniteQuery.refetchOnReconnect == false ||
!infiniteQuery.enabled) continue;
await infiniteQuery.refetchPages();
await Future.delayed(refetchOnReconnectDelay);
}
}
});
if (kIsMobile) {
SystemChannels.lifecycle.setMessageHandler((msg) async {
if (msg == 'AppLifecycleState.resumed') {
if (_canNotRefetchAfterWeGotTheApp) return null;
for (final query in this.cache.queries.toList()) {
if (query.refetchOnApplicationResume == false || !query.enabled)
continue;
await query.refetch();
await Future.delayed(refetchOnApplicationResumeDelay);
}
for (final infiniteQuery in this.cache.infiniteQueries.toList()) {
if (infiniteQuery.refetchOnApplicationResume == false ||
!infiniteQuery.enabled) continue;
await infiniteQuery.refetchPages();
await Future.delayed(refetchOnApplicationResumeDelay);
}
} else if (msg != null) {
updateWeLostTheApp();
}
return null;
});
}
}
DateTime? _weLostTheAppAt;
/// Returns the number of query is currently fetching or refetching
int get isFetching {
return cache.queries.fold<int>(
0,
(acc, query) {
if (query.isLoading || query.isRefetching) acc++;
return acc;
},
);
}
/// Provides the number of mutations that are running at the moment
int get isMutating {
return cache.mutations.fold<int>(
0,
(acc, mutation) {
if (mutation.isLoading) acc++;
return acc;
},
);
}
bool get _canNotRefetchAfterWeGotTheApp => (_weLostTheAppAt != null &&
_weLostTheAppAt!.difference(DateTime.now()) <= cache.cacheTime);
@protected
updateWeLostTheApp() {
_weLostTheAppAt = DateTime.now();
}
@protected
notifyWindowFocused() async {
if (kIsMobile || _canNotRefetchAfterWeGotTheApp) return;
for (final query in this.cache.queries.toList()) {
if (query.refetchOnWindowFocus == false || !query.enabled) continue;
await query.refetch();
await Future.delayed(refetchOnWindowFocusDelay);
}
for (final infiniteQuery in this.cache.infiniteQueries.toList()) {
if (infiniteQuery.refetchOnWindowFocus == false || !infiniteQuery.enabled)
continue;
await infiniteQuery.refetchPages();
await Future.delayed(refetchOnWindowFocusDelay);
}
}
void onQueriesUpdate<T extends Object, Outside>(
void Function(Query<T, Outside> query) listener,
) {
cache.on((event, changes) {
if (event == CacheEvent.query && changes is Query<T, Outside>) {
listener(changes);
}
});
}
void onMutationsUpdate<T extends Object, Outside>(
void Function(Mutation<T, Outside> mutation) listener,
) {
cache.on((event, changes) {
if (event == CacheEvent.mutation && changes is Mutation<T, Outside>) {
listener(changes);
}
});
}
void onInfiniteQueriesUpdate<T extends Object, Outside,
PageParam extends Object>(
void Function(InfiniteQuery<T, Outside, PageParam> infiniteQuery) listener,
) {
cache.on((event, changes) {
if (event == CacheEvent.infiniteQuery &&
changes is InfiniteQuery<T, Outside, PageParam>) {
listener(changes);
}
});
}
InfiniteQuery<T, Outside, PageParam>?
getInfiniteQuery<T extends Object, Outside, PageParam extends Object>(
String queryKey,
) {
return cache.infiniteQueries.firstWhereOrNull((infiniteQuery) {
return infiniteQuery.queryKey == queryKey &&
infiniteQuery is InfiniteQuery<T, Outside, PageParam>;
})?.cast<InfiniteQuery<T, Outside, PageParam>>();
}
/// Get a query by providing queryKey only
///
/// Useful for optimistic update or single query refetch
Query<T, Outside>? getQuery<T extends Object, Outside>(String queryKey) {
return cache.queries.firstWhereOrNull((query) {
return query.queryKey == queryKey && query is Query<T, Outside>;
})?.cast<Query<T, Outside>>();
}
/// Get a mutation by providing mutationKey only
///
/// Useful for mutation resets
Mutation<T, V>? getMutation<T extends Object, V>(String mutationKey) {
return cache.mutations.firstWhereOrNull((mutation) {
return mutation.mutationKey == mutationKey && mutation is Mutation<T, V>;
})?.cast<Mutation<T, V>>();
}
/// Sets [Query]'s data manually
///
/// Mostly used in combination with [onMutate] callback of
/// [MutationBuilder] & [useMutation] for optimistic updates
void setQueryData<T extends Object, Outside>(
String queryKey,
QueryUpdateFunction<T> updateCb,
) {
getQuery<T, Outside>(queryKey)?.setQueryData(updateCb);
}
/// Sets [InfiniteQuery]'s data manually
///
/// Mostly used in combination with [onMutate] callback of
/// [MutationBuilder] & [useMutation] for optimistic updates
void
setInfiniteQueryData<T extends Object, Outside, PageParam extends Object>(
String queryKey,
QueryUpdateFunction<Map<PageParam, T?>> updateCb,
) {
getInfiniteQuery<T, Outside, PageParam>(queryKey)?.setQueryData(updateCb);
}
/// resets all the queries matching the passed List of queryKeys
///
/// If an empty list of [queryKeys] is passed then all of the queries
/// will be reset
void resetQueries(List<String> queryKeys) {
for (final query in cache.queries) {
if (queryKeys.isNotEmpty && !queryKeys.contains(query.queryKey)) continue;
query.reset();
}
}
/// makes all the queries matching the passed List of queryKeys stale
///
/// If an empty list of [queryKeys] is passed then all of the queries
/// will be invalidated
void invalidateQueries(List<String> queryKeys) {
for (final query in cache.queries) {
if (queryKeys.isNotEmpty && queryKeys.contains(query.queryKey)) continue;
query.invalidate();
}
}
/// refetches all the queries matching the passed List of queryKeys
///
/// If an empty list of [queryKeys] is passed then all of the queries
/// will be refetched
Future<void> refetchQueries(List<String> queryKeys) async {
for (final query in cache.queries) {
if (queryKeys.isNotEmpty && queryKeys.contains(query.queryKey)) continue;
await query.refetch();
}
}
/// Removes all the queries matching the passed List of queryKeys
/// from the [QueryCache]
int removeQueries(List<String> queryKeys) {
int count = 0;
for (final query in cache.queries) {
if (queryKeys.isEmpty || !queryKeys.contains(query.queryKey)) continue;
cache.removeQuery(query);
count++;
}
return count;
}
Query<T, Outside> _createQueryWithDefaults<T extends Object, Outside>(
QueryJob<T, Outside> options,
Outside externalData, [
T? previousData,
]) {
final query = Query<T, Outside>.fromOptions(
options,
externalData: externalData,
previousData: previousData,
);
query.updateDefaultOptions(
cacheTime: cache.cacheTime,
staleTime: staleTime,
refetchInterval: refetchInterval,
refetchOnMount: refetchOnMount,
refetchOnReconnect: refetchOnReconnect,
refetchOnApplicationResume: refetchOnApplicationResume,
refetchOnWindowFocus: refetchOnWindowFocus,
);
return query;
}
InfiniteQuery<T, Outside, PageParam> _createInfiniteQueryWithDefaults<
T extends Object, Outside, PageParam extends Object>(
InfiniteQueryJob<T, Outside, PageParam> options,
Outside externalData,
) {
final infiniteQuery = InfiniteQuery<T, Outside, PageParam>.fromOptions(
options,
externalData: externalData,
);
infiniteQuery.updateDefaultOptions(
cacheTime: cache.cacheTime,
staleTime: staleTime,
refetchInterval: refetchInterval,
refetchOnMount: refetchOnMount,
refetchOnReconnect: refetchOnReconnect,
refetchOnApplicationResume: refetchOnApplicationResume,
refetchOnWindowFocus: refetchOnWindowFocus,
);
return infiniteQuery;
}
/// Creates/Updates a [Query] with the provided [QueryJob] and it's
/// [externalData] and listeners and mounts the [QueryBuilder] or
/// [useQuery] for the Query
Query<T, Outside> addQuery<T extends Object, Outside>(
QueryJob<T, Outside> queryJob, {
required Outside externalData,
required ValueKey<String> key,
final QueryListener<T>? onData,
final QueryListener<dynamic>? onError,
final T? previousData,
}) {
final prevQuery = getQuery<T, Outside>(queryJob.queryKey);
if (prevQuery != null) {
// run the query if its still not called or if externalData has
// changed
if (!isShallowEqual(
prevQuery.prevUsedExternalData,
externalData,
)) {
prevQuery.setExternalData(externalData);
}
prevQuery.mount(key);
if (onData != null) prevQuery.addDataListener(onData);
if (onError != null) prevQuery.addErrorListener(onError);
// mounting the widget that is using the query in the prevQuery
return prevQuery;
}
final query = _createQueryWithDefaults<T, Outside>(
queryJob,
externalData,
previousData,
);
if (onData != null) query.addDataListener(onData);
if (onError != null) query.addErrorListener(onError);
query.mount(key);
cache.addQuery(query);
return query;
}
/// Creates/Updates a [InfiniteQuery] with the provided
/// [InfiniteQueryJob] and it's [externalData] and listeners and mounts
/// the [InfiniteQueryBuilder] or [useInfiniteQuery] for the it
InfiniteQuery<T, Outside, PageParam>
addInfiniteQuery<T extends Object, Outside, PageParam extends Object>(
InfiniteQueryJob<T, Outside, PageParam> infiniteQueryJob, {
required Outside externalData,
required ValueKey<String> key,
final InfiniteQueryListeners<T, PageParam>? onData,
final InfiniteQueryListeners<dynamic, PageParam>? onError,
}) {
final prevInfiniteQuery =
getInfiniteQuery<T, Outside, PageParam>(infiniteQueryJob.queryKey);
if (prevInfiniteQuery != null) {
// run the query if its still not called or if externalData has
// changed
if (!isShallowEqual(
prevInfiniteQuery.prevUsedExternalData,
externalData,
)) {
prevInfiniteQuery.setExternalData(externalData);
}
prevInfiniteQuery.mount(key);
if (onData != null) prevInfiniteQuery.addDataListener(onData);
if (onError != null) prevInfiniteQuery.addErrorListener(onError);
// mounting the widget that is using the query in the prevQuery
return prevInfiniteQuery;
}
final infiniteQuery =
_createInfiniteQueryWithDefaults<T, Outside, PageParam>(
infiniteQueryJob,
externalData,
);
if (onData != null) infiniteQuery.addDataListener(onData);
if (onError != null) infiniteQuery.addErrorListener(onError);
infiniteQuery.mount(key);
cache.addInfiniteQuery(infiniteQuery);
return infiniteQuery;
}
/// Creates/Updates a [Mutation] with the provided
/// [MutationJob] and it's listeners. Mounts
/// the [MutationBuilder] or [useMutation] for the it
Mutation<T, V> addMutation<T extends Object, V>(
MutationJob<T, V> mutationJob, {
final MutationListener<T, V>? onData,
final MutationListener<dynamic, V>? onError,
final MutationListenerReturnable<V, dynamic>? onMutate,
required ValueKey<String> key,
}) {
final prevMutation = getMutation<T, V>(mutationJob.mutationKey);
if (prevMutation != null) {
if (onData != null) prevMutation.addDataListener(onData);
if (onError != null) prevMutation.addErrorListener(onError);
if (onMutate != null) prevMutation.addMutateListener(onMutate);
prevMutation.mount(key);
return prevMutation;
} else {
final mutation = Mutation<T, V>.fromOptions(
mutationJob,
);
if (onData != null) mutation.addDataListener(onData);
if (onError != null) mutation.addErrorListener(onError);
if (onMutate != null) mutation.addMutateListener(onMutate);
mutation.updateDefaultOptions(cacheTime: cache.cacheTime);
mutation.mount(key);
cache.addMutation(mutation);
return mutation;
}
}
/// Creates/Updates a [Query] with the provided [QueryJob] and it's
/// [externalData] and listeners and mounts the [QueryBuilder] or
/// [useQuery] for the Query
///
/// It also fetches/refetches the [Query] strategically/based on changes
Future<T?> fetchQuery<T extends Object, Outside>(
QueryJob<T, Outside> options, {
required Outside externalData,
final QueryListener<T>? onData,
final QueryListener<dynamic>? onError,
required ValueKey<String> key,
}) async {
final prevQuery = getQuery<T, Outside>(options.queryKey);
if (prevQuery != null) {
// run the query if its still not called or if externalData has
// changed
final hasExternalDataChanged = !isShallowEqual(
prevQuery.prevUsedExternalData,
externalData,
);
prevQuery.mount(key);
if (onData != null) prevQuery.addDataListener(onData);
if (onError != null) prevQuery.addErrorListener(onError);
if (!prevQuery.hasData || hasExternalDataChanged) {
if (hasExternalDataChanged) prevQuery.setExternalData(externalData);
return await prevQuery.refetch();
}
// mounting the widget that is using the query in the prevQuery
return prevQuery.data;
}
final query = _createQueryWithDefaults<T, Outside>(
options,
externalData,
);
query.mount(key);
cache.addQuery(query);
return await query.fetch();
}
/// Finds the closest instance of [QueryBowl] for the provided
/// [BuildContext]
static QueryBowl of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<QueryBowlScope>()!.bowl;
}
}
/// A simple [InheritedWidget] that does the job of injecting [QueryBowl]
/// into context
///
/// ```dart
/// Widget build(BuildContext context) {
/// return QueryBowlScope(
/// bowl: QueryBowl(),
/// child: MaterialApp(/*...other stuff...*/),
/// );
/// }
/// ```
class QueryBowlScope extends InheritedWidget {
final QueryBowl bowl;
QueryBowlScope({
required this.bowl,
required Widget child,
Key? key,
}) : super(
key: key,
child: MouseRegion(
onEnter: (event) {
bowl.notifyWindowFocused();
},
onExit: (event) {
bowl.updateWeLostTheApp();
},
child: child,
),
);
@override
bool updateShouldNotify(covariant oldWidget) => false;
}
@@ -1,156 +0,0 @@
import 'package:fl_query/src/models/query_job.dart';
import 'package:fl_query/src/query.dart';
import 'package:fl_query/src/query_bowl.dart';
import 'package:fl_query/src/utils.dart';
import 'package:flutter/cupertino.dart';
class QueryBuilder<T extends Object, Outside> extends StatefulWidget {
final Widget Function(BuildContext context, Query<T, Outside> query) builder;
final QueryJob<T, Outside> job;
final Outside externalData;
/// Called when the query returns new data, on query
/// refetch or query gets expired
final QueryListener<T>? onData;
/// Called when the query returns error
final QueryListener<dynamic>? onError;
const QueryBuilder({
required this.job,
required this.externalData,
required this.builder,
this.onData,
this.onError,
Key? key,
}) : super(key: key);
@override
_QueryBuilderState<T, Outside> createState() =>
_QueryBuilderState<T, Outside>();
}
class _QueryBuilderState<T extends Object, Outside>
extends State<QueryBuilder<T, Outside>> {
Query<T, Outside>? query;
late final ValueKey<String> uKey;
@override
void initState() {
super.initState();
uKey = ValueKey<String>(uuid.v4());
WidgetsBinding.instance.addPostFrameCallback((_) {
init();
QueryBowl.of(context).onQueriesUpdate<T, Outside>(
(query) {
if (query.queryKey != widget.job.queryKey) return;
if (mounted)
setState(() {
this.query = query;
});
},
);
});
}
void init([T? previousData]) async {
final bowl = QueryBowl.of(context);
final prevQuery = bowl.getQuery<T, Outside>(widget.job.queryKey);
query = bowl.addQuery<T, Outside>(
widget.job,
externalData: widget.externalData,
previousData: previousData,
key: uKey,
onData: widget.onData,
onError: widget.onError,
);
final hasExternalDataChanged = prevQuery != null
? !isShallowEqual(
prevQuery.externalData,
prevQuery.prevUsedExternalData,
)
: !isShallowEqual(
query!.externalData,
query!.prevUsedExternalData,
);
if (query!.fetched && hasExternalDataChanged) {
await query!.refetch();
} else if (!query!.fetched) {
await query!.fetch();
}
}
@override
void didUpdateWidget(covariant oldWidget) {
final bowl = QueryBowl.of(context);
final hasOnErrorChanged =
oldWidget.onError != widget.onError && oldWidget.onError != null;
final hasOnDataChanged =
oldWidget.onData != widget.onData && oldWidget.onData != null;
// re-init the query-builder when new queryJob is appended
if (oldWidget.job.queryKey != widget.job.queryKey) {
_queryDispose();
/// setting the new query's initial data as prev query's data
/// when [job.keepPreviousData] is true and both are dynamic
if (oldWidget.job.isDynamic &&
widget.job.isDynamic &&
oldWidget.job.keepPreviousData == true &&
widget.job.keepPreviousData == true) {
init(query?.data);
} else {
init();
}
} else if (!isShallowEqual(oldWidget.externalData, widget.externalData)) {
if (widget.job.refetchOnExternalDataChange ??
bowl.refetchOnExternalDataChange) {
bowl.fetchQuery(
widget.job,
externalData: widget.externalData,
onData: widget.onData,
onError: widget.onError,
key: uKey,
);
} else {
bowl
.getQuery(widget.job.queryKey)
?.setExternalData(widget.externalData);
}
if (hasOnDataChanged) query?.removeDataListener(oldWidget.onData!);
if (hasOnErrorChanged) query?.removeErrorListener(oldWidget.onError!);
} else {
if (hasOnDataChanged) {
query?.removeDataListener(oldWidget.onData!);
if (widget.onData != null) query?.addDataListener(widget.onData!);
}
if (hasOnErrorChanged) {
query?.removeErrorListener(oldWidget.onError!);
if (widget.onError != null) query?.addErrorListener(widget.onError!);
}
}
super.didUpdateWidget(oldWidget);
}
_queryDispose() {
query?.unmount(uKey);
if (widget.onData != null) query?.removeDataListener(widget.onData!);
if (widget.onError != null) query?.removeErrorListener(widget.onError!);
}
@override
void dispose() {
_queryDispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (query == null) return Container();
return widget.builder(context, query!);
}
}
-146
View File
@@ -1,146 +0,0 @@
import 'package:collection/collection.dart';
import 'package:fl_query/fl_query.dart';
import 'package:fl_query/src/base_operation.dart';
typedef ReadonlySet<E> = UnmodifiableSetView<E>;
enum CacheEvent {
clearCache,
query,
infiniteQuery,
mutation,
}
typedef CacheUpdateListener<T> = void Function(CacheEvent event, T? changes);
class QueryCache {
/// Removes inactive queries after provided duration of [cacheTime]
final Duration cacheTime;
QueryCache({Duration? cacheTime})
: cacheTime = cacheTime ?? const Duration(minutes: 5) {}
final Set<Query> _queries = {};
final Set<InfiniteQuery> _infiniteQueries = {};
final Set<Mutation> _mutations = {};
final Set<CacheUpdateListener<BaseOperation>> _listeners = {};
Set<Query> get queries => _queries;
Set<InfiniteQuery> get infiniteQueries => _infiniteQueries;
Set<Mutation> get mutations => _mutations;
ReadonlySet<CacheUpdateListener<BaseOperation>> get listeners =>
UnmodifiableSetView(_listeners);
_notifyListeners<T extends Object>(CacheEvent event, changes) {
_listeners.forEach((listener) => listener(event, changes));
}
_listenToQueryChanges(Query query) {
query.addListener(() {
if (query.isInactive) {
_queries.removeWhere((el) {
if (el.queryKey != query.queryKey) {
el.dispose();
return true;
}
return false;
});
_notifyListeners(CacheEvent.query, null);
} else {
_notifyListeners(CacheEvent.query, query);
}
});
}
_listenToInfiniteQueryChanges(InfiniteQuery infiniteQuery) {
infiniteQuery.addListener(() {
if (infiniteQuery.isInactive) {
_infiniteQueries.removeWhere((el) {
if (el.queryKey != infiniteQuery.queryKey) {
el.dispose();
return true;
}
return false;
});
_notifyListeners(CacheEvent.infiniteQuery, null);
} else {
_notifyListeners(CacheEvent.infiniteQuery, infiniteQuery);
}
});
}
_listenToMutationChanges(Mutation mutation) {
mutation.addListener(() {
if (mutation.isInactive) {
_mutations.removeWhere(
(el) {
if (el.mutationKey != mutation.mutationKey) {
el.dispose();
return true;
}
return false;
},
);
_notifyListeners(CacheEvent.mutation, null);
} else {
_notifyListeners(CacheEvent.mutation, mutation);
}
});
}
void addQuery(Query query) {
_queries.add(query);
_listenToQueryChanges(query);
_notifyListeners(CacheEvent.query, query);
}
void addInfiniteQuery(InfiniteQuery infiniteQuery) {
_infiniteQueries.add(infiniteQuery);
_listenToInfiniteQueryChanges(infiniteQuery);
_notifyListeners(CacheEvent.infiniteQuery, infiniteQuery);
}
void addMutation(Mutation mutation) {
_mutations.add(mutation);
_listenToMutationChanges(mutation);
_notifyListeners(CacheEvent.mutation, mutation);
}
void removeQuery(Query query) {
query.dispose();
_queries.remove(query);
_notifyListeners(CacheEvent.query, null);
}
void removeInfiniteQuery(InfiniteQuery infiniteQuery) {
infiniteQuery.dispose();
_infiniteQueries.remove(infiniteQuery);
_notifyListeners(CacheEvent.infiniteQuery, null);
}
void removeMutation(Mutation mutation) {
mutation.dispose();
_mutations.remove(mutation);
_notifyListeners(CacheEvent.mutation, null);
}
void clearCache() {
_infiniteQueries.forEach((el) => el.dispose());
_queries.forEach((el) => el.dispose());
_mutations.forEach((el) => el.dispose());
_infiniteQueries.clear();
_queries.clear();
_mutations.clear();
_notifyListeners(CacheEvent.clearCache, null);
}
void on(CacheUpdateListener<BaseOperation?> listener) {
_listeners.add(listener);
}
void off(CacheUpdateListener<BaseOperation?> listener) {
_listeners.remove(listener);
}
}
-72
View File
@@ -1,72 +0,0 @@
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:fl_query/src/query.dart';
import 'package:uuid/uuid.dart';
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart';
Future<void> callQueryListeners<T>(Set<QueryListener<T>> listeners, T data) {
return Future.wait(listeners.map(
(listener) => Future.value(listener(data)),
));
// for (final listener in listeners) {
// await listener(data);
// }
}
const uuid = Uuid();
bool isShallowEqualList(List list1, List list2) {
return list1.asMap().entries.every((l1Entry) {
return l1Entry.value == list2[l1Entry.key];
});
}
bool isShallowEqualSet(Set list1, Set list2) {
return isShallowEqualList(list1.toList(), list2.toList());
}
bool isShallowEqualMap(Map list1, Map list2) {
return list1.entries.every((l1Entry) {
return l1Entry.value == list2[l1Entry.key];
});
}
bool isShallowEqual(dynamic obj1, dynamic obj2) {
if (obj1 == null && obj2 == null) return true;
if ((obj1 == null && obj2 != null) || (obj2 == null && obj1 != null))
return false;
if (obj1 is List && obj2 is List) {
return isShallowEqualList(obj1, obj2);
} else if (obj1 is Set && obj2 is Set) {
return isShallowEqualSet(obj1, obj2);
} else if (obj1 is Map && obj2 is Map) {
return isShallowEqualMap(obj1, obj2);
} else {
// for other types basically comparing references for non primitive
// types. And primitives are always compared by value
return obj1 == obj2;
}
}
bool isConnectedToInternet(ConnectivityResult result) {
return [
ConnectivityResult.ethernet,
ConnectivityResult.mobile,
ConnectivityResult.wifi,
].contains(result);
}
String getVariable(String queryKey) {
return queryKey.split("#").last;
}
final kIsDesktop = kIsLinux || kIsWindows || kIsMacOS;
final kIsMobile = kIsAndroid || kIsIOS;
final kIsMacOS = kIsWeb ? false : Platform.isMacOS;
final kIsLinux = kIsWeb ? false : Platform.isLinux;
final kIsAndroid = kIsWeb ? false : Platform.isAndroid;
final kIsIOS = kIsWeb ? false : Platform.isIOS;
final kIsWindows = kIsWeb ? false : Platform.isWindows;