feat(infinite-query): add refetchPages, refetchOnStale, refetchOnMount support
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user