feat(infinite-query): add refetchPages, refetchOnStale, refetchOnMount support

This commit is contained in:
Kingkor Roy Tirtho
2022-09-14 12:49:30 +06:00
parent 61958c7d94
commit 95b1837620
20 changed files with 257 additions and 104 deletions
+6 -12
View File
@@ -71,7 +71,7 @@ abstract class BaseQuery<T extends Object, Outside, Error>
if (onError != null) _onErrorListeners.add(onError);
if (refetchInterval != null && refetchInterval != Duration.zero) {
_refetchIntervalTimer = _createRefetchTimer();
_refetchIntervalTimer = createRefetchTimer();
}
}
// all getters & setters
@@ -79,16 +79,8 @@ abstract class BaseQuery<T extends Object, Outside, Error>
Outside get externalData => _externalData;
Outside? get prevUsedExternalData => _prevUsedExternalData;
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 isInternetConnected()) await refetch();
},
);
}
@protected
Timer createRefetchTimer();
/// Calls the task function & doesn't check if there's already
/// cached data available
@@ -205,7 +197,9 @@ abstract class BaseQuery<T extends Object, Outside, Error>
return await execute().then((_) => data);
}
@protected
FutureOr<void> setData();
@protected
void setError(dynamic);
/// Sets the [externalData] from outside of the query
@@ -251,7 +245,7 @@ abstract class BaseQuery<T extends Object, Outside, Error>
refetchInterval != Duration.zero) {
this.refetchInterval = refetchInterval;
_refetchIntervalTimer?.cancel();
_refetchIntervalTimer = _createRefetchTimer();
_refetchIntervalTimer = createRefetchTimer();
}
if (this.cacheTime == Duration(minutes: 5) && cacheTime != null)
this.cacheTime = cacheTime;
+62 -3
View File
@@ -4,6 +4,8 @@ import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:fl_query/src/base_query.dart';
import 'package:fl_query/src/models/infinite_query_job.dart';
import 'package:fl_query/src/query.dart';
import 'package:flutter/cupertino.dart';
import 'package:queue/queue.dart';
typedef InfiniteQueryTaskFunction<T extends Object, Outside,
PageParam extends Object>
@@ -91,6 +93,19 @@ class InfiniteQuery<T extends Object, Outside, PageParam extends Object>
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 isInternetConnected()) await refetchPages();
},
);
}
Future<T?> fetchNextPage([
InfiniteQueryPageParamFunction<T, PageParam>? getNextPageParam,
]) async {
@@ -110,10 +125,11 @@ class InfiniteQuery<T extends Object, Outside, PageParam extends Object>
_hasNextPage = false;
notifyListeners();
return null;
} else {
_hasNextPage = true;
_currentParam = nextParam;
return await refetch().then((data) => data?[_currentParam]);
}
_hasNextPage = true;
_currentParam = nextParam;
return await refetch().then((data) => data?[_currentParam]);
} finally {
_isFetchingNextPage = false;
notifyListeners();
@@ -154,11 +170,53 @@ class InfiniteQuery<T extends Object, Outside, PageParam extends Object>
}
}
Future<List<T>> refetchPages([
bool Function(T? page, int index, 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, pages.indexOf(page), 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
// TODO: implement debugLabel
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) {
this.isInternetConnected().then((isConnected) async {
if (isConnected) await refetchPages();
});
}
}
@override
@protected
void setData() async {
if (data == null) data = Map();
data?[_currentParam] = await task(
@@ -169,6 +227,7 @@ class InfiniteQuery<T extends Object, Outside, PageParam extends Object>
}
@override
@protected
void setError(specError) {
if (error is! Map) error = Map();
error?[_currentParam] = specError;
@@ -48,14 +48,77 @@ class _InfiniteQueryBuilderState<T extends Object, Outside,
final hasExternalDataChanged = infiniteQuery!.externalData != null &&
infiniteQuery!.prevUsedExternalData != null &&
!isShallowEqual(
infiniteQuery!.externalData!, infiniteQuery!.prevUsedExternalData!);
infiniteQuery!.externalData!,
infiniteQuery!.prevUsedExternalData!,
);
if (infiniteQuery!.fetched && hasExternalDataChanged) {
await infiniteQuery!.refetch();
await infiniteQuery!.refetchPages();
} else if (!infiniteQuery!.fetched) {
await infiniteQuery!.fetch();
}
}
@override
void didUpdateWidget(covariant oldWidget) {
// final hasOnErrorChanged = false;
// oldWidget.onError != widget.onError && oldWidget.onError != null;
// final hasOnDataChanged = false;
// 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 (oldWidget.externalData != null &&
widget.externalData != null &&
!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) 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);
}
_infiniteQueryDispose() {
infiniteQuery?.unmount(uKey);
// if (widget.onData != null) query?.removeDataListener(widget.onData!);
// if (widget.onError != null) query?.removeErrorListener(widget.onError!);
}
@override
Widget build(BuildContext context) {
queryBowl = QueryBowl.of(context);
+12
View File
@@ -85,6 +85,18 @@ class Query<T extends Object, Outside> extends BaseQuery<T, Outside, dynamic> {
queryKey: options.queryKey,
);
@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 isInternetConnected()) await refetch();
},
);
}
/// can be used to update the data manually. Can be useful when used
/// together with mutations to perform optimistic updates or manual data
/// updates
+36 -33
View File
@@ -114,6 +114,12 @@ class _QueryBowlScopeState extends State<QueryBowlScope> {
await query.refetch();
await Future.delayed(widget.refetchOnReconnectDelay);
}
for (final infiniteQuery in infiniteQueries) {
if (infiniteQuery.refetchOnReconnect == false ||
!infiniteQuery.enabled) continue;
await infiniteQuery.refetchPages();
await Future.delayed(widget.refetchOnReconnectDelay);
}
}
});
}
@@ -169,10 +175,10 @@ class _QueryBowlScopeState extends State<QueryBowlScope> {
// basically garbage collecting queries
setState(() {
infiniteQueries = Set.from(
// infiniteQuery.isInactive
// ? infiniteQueries.where((el) => el.queryKey != infiniteQuery.queryKey)
// : infiniteQuery,
infiniteQueries,
infiniteQuery.isInactive
? infiniteQueries
.where((el) => el.queryKey != infiniteQuery.queryKey)
: infiniteQueries,
);
});
});
@@ -350,25 +356,25 @@ class QueryBowl extends InheritedWidget {
return query;
}
// InfiniteQuery<T, Outside, PageParam> _createInfiniteQueryWithDefaults<
// T extends Object, Outside, PageParam extends Object>(
// InfiniteQueryJob<T, Outside, PageParam> options,
// Outside externalData,
// ) {
// final query = InfiniteQuery<T, Outside, PageParam>.fromOptions(
// options,
// externalData: externalData,
// queryBowl: this,
// );
// query.updateDefaultOptions(
// cacheTime: cacheTime,
// staleTime: staleTime,
// refetchInterval: refetchInterval,
// refetchOnMount: refetchOnMount,
// refetchOnReconnect: refetchOnReconnect,
// );
// 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,
queryBowl: this,
);
infiniteQuery.updateDefaultOptions(
cacheTime: cacheTime,
staleTime: staleTime,
refetchInterval: refetchInterval,
refetchOnMount: refetchOnMount,
refetchOnReconnect: refetchOnReconnect,
);
return infiniteQuery;
}
Future<T?> prefetchQuery<T extends Object, Outside>(
QueryJob<T, Outside> options, {
@@ -451,18 +457,15 @@ class QueryBowl extends InheritedWidget {
// mounting the widget that is using the query in the prevQuery
return prevInfiniteQuery;
}
// _createQueryWithDefaults<T, Outside, PageParam>(
// infiniteQueryJob,
// externalData,
// );
final infiniteQuery = InfiniteQuery<T, Outside, PageParam>.fromOptions(
final infiniteQuery =
_createInfiniteQueryWithDefaults<T, Outside, PageParam>(
infiniteQueryJob,
externalData: externalData,
queryBowl: this,
externalData,
);
// if (onData != null) infiniteQuery.addDataListener(onData);
// if (onError != null) infiniteQuery.addErrorListener(onError);
// infiniteQuery.mount(key);
if (onData != null) infiniteQuery.addDataListener(onData);
if (onError != null) infiniteQuery.addErrorListener(onError);
infiniteQuery.mount(key);
_addInfiniteQuery<T, Outside, PageParam>(infiniteQuery);
return infiniteQuery;
}
+5 -1
View File
@@ -57,7 +57,11 @@ class _QueryBuilderState<T extends Object, Outside>
);
final hasExternalDataChanged = query!.externalData != null &&
query!.prevUsedExternalData != null &&
!isShallowEqual(query!.externalData!, query!.prevUsedExternalData!);
!isShallowEqual(
query!.externalData!,
query!.prevUsedExternalData!,
);
if (query!.fetched && hasExternalDataChanged) {
await query!.refetch();
} else if (!query!.fetched) {