From f47ca98472ccd3eecf49ae348225332c8a2f61ea Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 16 Sep 2022 14:32:23 +0600 Subject: [PATCH] feat(infinite-query): onData and onError listener support --- .../example/.flutter-plugins-dependencies | 2 +- packages/fl_query/lib/src/base_query.dart | 58 +++++++----- packages/fl_query/lib/src/infinite_query.dart | 64 +++++++++---- .../lib/src/infinite_query_builder.dart | 47 ++++++---- packages/fl_query/lib/src/query.dart | 7 +- packages/fl_query/lib/src/query_bowl.dart | 4 +- .../lib/src/use_infinite_query.dart | 91 +++++++++---------- 7 files changed, 165 insertions(+), 108 deletions(-) diff --git a/packages/fl_query/example/.flutter-plugins-dependencies b/packages/fl_query/example/.flutter-plugins-dependencies index 450a8d6..e23270d 100644 --- a/packages/fl_query/example/.flutter-plugins-dependencies +++ b/packages/fl_query/example/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"connectivity_plus","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus-2.3.6/","native_build":true,"dependencies":[]}],"android":[{"name":"connectivity_plus","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus-2.3.6/","native_build":true,"dependencies":[]}],"macos":[{"name":"connectivity_plus_macos","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_macos-1.2.4/","native_build":true,"dependencies":[]}],"linux":[{"name":"connectivity_plus_linux","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_linux-1.3.1/","native_build":false,"dependencies":[]}],"windows":[{"name":"connectivity_plus_windows","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_windows-1.2.2/","native_build":true,"dependencies":[]}],"web":[{"name":"connectivity_plus_web","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_web-1.2.3/","dependencies":[]}]},"dependencyGraph":[{"name":"connectivity_plus","dependencies":["connectivity_plus_linux","connectivity_plus_macos","connectivity_plus_web","connectivity_plus_windows"]},{"name":"connectivity_plus_linux","dependencies":[]},{"name":"connectivity_plus_macos","dependencies":[]},{"name":"connectivity_plus_web","dependencies":[]},{"name":"connectivity_plus_windows","dependencies":[]}],"date_created":"2022-09-14 13:12:02.969795","version":"3.3.0"} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"connectivity_plus","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus-2.3.6/","native_build":true,"dependencies":[]}],"android":[{"name":"connectivity_plus","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus-2.3.6/","native_build":true,"dependencies":[]}],"macos":[{"name":"connectivity_plus_macos","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_macos-1.2.4/","native_build":true,"dependencies":[]}],"linux":[{"name":"connectivity_plus_linux","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_linux-1.3.1/","native_build":false,"dependencies":[]}],"windows":[{"name":"connectivity_plus_windows","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_windows-1.2.2/","native_build":true,"dependencies":[]}],"web":[{"name":"connectivity_plus_web","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_web-1.2.3/","dependencies":[]}]},"dependencyGraph":[{"name":"connectivity_plus","dependencies":["connectivity_plus_linux","connectivity_plus_macos","connectivity_plus_web","connectivity_plus_windows"]},{"name":"connectivity_plus_linux","dependencies":[]},{"name":"connectivity_plus_macos","dependencies":[]},{"name":"connectivity_plus_web","dependencies":[]},{"name":"connectivity_plus_windows","dependencies":[]}],"date_created":"2022-09-15 12:05:12.417424","version":"3.3.0"} \ No newline at end of file diff --git a/packages/fl_query/lib/src/base_query.dart b/packages/fl_query/lib/src/base_query.dart index d5b5d76..33a92a4 100644 --- a/packages/fl_query/lib/src/base_query.dart +++ b/packages/fl_query/lib/src/base_query.dart @@ -26,8 +26,10 @@ abstract class BaseQuery QueryStatus status; - final Set _onDataListeners = Set(); - final Set _onErrorListeners = Set(); + @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 @@ -67,8 +69,8 @@ abstract class BaseQuery _connectivity = connectivity ?? Connectivity(), _previousData = previousData, super(data: previousData ?? initialData) { - if (onData != null) _onDataListeners.add(onData); - if (onError != null) _onErrorListeners.add(onError); + if (onData != null) onDataListeners.add(onData); + if (onError != null) onErrorListeners.add(onError); if (refetchInterval != null && refetchInterval != Duration.zero) { _refetchIntervalTimer = createRefetchTimer(); @@ -92,17 +94,13 @@ abstract class BaseQuery _prevUsedExternalData = _externalData; updatedAt = DateTime.now(); status = QueryStatus.success; - for (final onData in _onDataListeners) { - onData(data!); - } + await notifyDataListeners(); notifyListeners(); } catch (e) { if (retries == 0) { status = QueryStatus.error; setError(e); - for (final onError in _onErrorListeners) { - onError(error); - } + await notifyErrorListeners(); notifyListeners(); } else { // retrying for retry count if failed for the first time @@ -112,18 +110,14 @@ abstract class BaseQuery await setData(); _prevUsedExternalData = _externalData; status = QueryStatus.success; - for (final onData in _onDataListeners) { - await onData(data!); - } + await notifyDataListeners(); notifyListeners(); break; } catch (e) { if (retryAttempts == retries) { status = QueryStatus.error; setError(e); - for (final onError in _onErrorListeners) { - await onError(error); - } + await notifyErrorListeners(); notifyListeners(); break; } @@ -135,19 +129,19 @@ abstract class BaseQuery } void addDataListener(listener) { - _onDataListeners.add(listener); + onDataListeners.add(listener); } void addErrorListener(listener) { - _onErrorListeners.add(listener); + onErrorListeners.add(listener); } void removeDataListener(listener) { - _onDataListeners.remove(listener); + onDataListeners.remove(listener); } void removeErrorListener(listener) { - _onErrorListeners.remove(listener); + onErrorListeners.remove(listener); } /// fetches data or runs the provided task initially @@ -185,6 +179,13 @@ abstract class BaseQuery /// the current available [data] instead of running the task to prevent /// race conditions Future refetch() async { + if (queryKey == "category-playlists#user-featured-playlists") { + print( + "Refetch Count of ${queryKey}-> ${refetchCount}", + ); + print(StackTrace.current); + } + /// if isLoading/isRefetching is true that means its already fetching/ /// refetching. So [_execute] again can create a race condition if (isRefetching || isLoading) return data; @@ -201,6 +202,19 @@ abstract class BaseQuery FutureOr setData(); @protected void setError(dynamic); + @protected + FutureOr notifyDataListeners() async { + for (final onData in onDataListeners) { + await onData(error); + } + } + + @protected + FutureOr notifyErrorListeners() async { + for (final onError in onErrorListeners) { + await onError(error); + } + } /// Sets the [externalData] from outside of the query /// @@ -222,8 +236,8 @@ abstract class BaseQuery fetched = false; status = QueryStatus.idle; retryAttempts = 0; - _onDataListeners.clear(); - _onErrorListeners.clear(); + onDataListeners.clear(); + onErrorListeners.clear(); mounts.clear(); } diff --git a/packages/fl_query/lib/src/infinite_query.dart b/packages/fl_query/lib/src/infinite_query.dart index a7f9025..a2a402d 100644 --- a/packages/fl_query/lib/src/infinite_query.dart +++ b/packages/fl_query/lib/src/infinite_query.dart @@ -14,6 +14,10 @@ typedef InfiniteQueryTaskFunction = FutureOr + Function(T page, PageParam pageParam, List pages); + typedef InfiniteQueryPageParamFunction = FutureOr Function(T lastPage, PageParam lastParam); @@ -38,6 +42,10 @@ class InfiniteQuery PageParam _currentParam; + final Set> onDataListeners = Set(); + final Set> onErrorListeners = + Set(); + InfiniteQuery({ required super.queryKey, required this.task, @@ -55,8 +63,8 @@ class InfiniteQuery super.enabled, super.previousData, super.connectivity, - super.onData, - super.onError, + InfiniteQueryListeners? super.onData, + InfiniteQueryListeners? super.onError, required T? initialPage, this.getNextPageParam, this.getPreviousPageParam, @@ -67,8 +75,8 @@ class InfiniteQuery InfiniteQueryJob options, { required super.queryBowl, required Outside externalData, - QueryListener? onData, - QueryListener? onError, + InfiniteQueryListeners? onData, + InfiniteQueryListeners? onError, }) : task = options.task, _currentParam = options.initialParam, getNextPageParam = options.getNextPageParam, @@ -114,13 +122,16 @@ class InfiniteQuery isFetchingPreviousPage || isLoading || isRefetching) return null; - if (data == null || data?[_currentParam] == null) execute(); + final page = data?[_currentParam]; + if (data == null || page == null) await execute(); _isFetchingNextPage = true; _isFetchingPreviousPage = false; - final nextParam = await (getNextPageParam ?? this.getNextPageParam)?.call( - data![_currentParam]!, - _currentParam, - ); + final nextParam = page != null + ? await (getNextPageParam ?? this.getNextPageParam)?.call( + page, + _currentParam, + ) + : null; if (nextParam == null) { _hasNextPage = false; notifyListeners(); @@ -128,7 +139,7 @@ class InfiniteQuery } else { _hasNextPage = true; _currentParam = nextParam; - return await refetch().then((data) => data?[_currentParam]); + return await fetch().then((_) => data?[_currentParam]); } } finally { _isFetchingNextPage = false; @@ -147,12 +158,14 @@ class InfiniteQuery _isFetchingPreviousPage = true; _isFetchingNextPage = false; notifyListeners(); - if (data?[_currentParam] == null) execute(); - final prevParam = - await (getPreviousPageParam ?? this.getPreviousPageParam)?.call( - data![_currentParam]!, - _currentParam, - ); + 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(); @@ -160,7 +173,7 @@ class InfiniteQuery } _hasPreviousPage = true; _currentParam = prevParam; - return await refetch().then((_) => data?[_currentParam]); + return await fetch().then((_) => data?[_currentParam]); } catch (e) { print("[InfiniteQuery.fetchPreviousPage]: $e"); rethrow; @@ -233,6 +246,23 @@ class InfiniteQuery error?[_currentParam] = specError; } + @override + @protected + FutureOr notifyDataListeners() async { + for (var onData in onDataListeners) { + if (data?[_currentParam] == null) continue; + onData.call(data![_currentParam]!, _currentParam, pages); + } + } + + @override + @protected + FutureOr notifyErrorListeners() async { + for (var onError in onErrorListeners) { + onError.call(error?[_currentParam], _currentParam, errors); + } + } + @override bool operator ==(other) { return (other is InfiniteQuery && diff --git a/packages/fl_query/lib/src/infinite_query_builder.dart b/packages/fl_query/lib/src/infinite_query_builder.dart index 2f139b7..973174b 100644 --- a/packages/fl_query/lib/src/infinite_query_builder.dart +++ b/packages/fl_query/lib/src/infinite_query_builder.dart @@ -14,10 +14,15 @@ class InfiniteQueryBuilder ) builder; final InfiniteQueryJob job; final Outside externalData; + final InfiniteQueryListeners? onData; + final InfiniteQueryListeners? onError; + InfiniteQueryBuilder({ required this.job, required this.builder, required this.externalData, + this.onData, + this.onError, Key? key, }) : super(key: key); @@ -63,10 +68,10 @@ class _InfiniteQueryBuilderState = FutureOr Function(T? oldData); class Query extends BaseQuery { QueryTaskFunction task; + final Set> onDataListeners = Set(); + final Set> onErrorListeners = Set(); + Query({ required super.queryKey, required this.task, @@ -56,8 +59,8 @@ class Query extends BaseQuery { super.previousData, super.connectivity, super.initialData, - super.onData, - super.onError, + QueryListener? super.onData, + QueryListener? super.onError, }); Query.fromOptions( diff --git a/packages/fl_query/lib/src/query_bowl.dart b/packages/fl_query/lib/src/query_bowl.dart index 1d37758..a455740 100644 --- a/packages/fl_query/lib/src/query_bowl.dart +++ b/packages/fl_query/lib/src/query_bowl.dart @@ -434,8 +434,8 @@ class QueryBowl extends InheritedWidget { InfiniteQueryJob infiniteQueryJob, { required Outside externalData, required ValueKey key, - final QueryListener? onData, - final QueryListener? onError, + final InfiniteQueryListeners? onData, + final InfiniteQueryListeners? onError, }) { final prevInfiniteQuery = _infiniteQueries.firstWhereOrNull( (q) => q.queryKey == infiniteQueryJob.queryKey, diff --git a/packages/fl_query_hooks/lib/src/use_infinite_query.dart b/packages/fl_query_hooks/lib/src/use_infinite_query.dart index 59f607a..60a392f 100644 --- a/packages/fl_query_hooks/lib/src/use_infinite_query.dart +++ b/packages/fl_query_hooks/lib/src/use_infinite_query.dart @@ -10,18 +10,18 @@ InfiniteQuery required InfiniteQueryJob job, required Outside externalData, - // /// Called when the query returns new data, on query - // /// refetch or query gets expired - // QueryListener? onData, + /// Called when the query returns new data, on query + /// refetch or query gets expired + final InfiniteQueryListeners? onData, - // /// Called when the query returns error - // QueryListener? onError, + /// Called when the query returns error + final InfiniteQueryListeners? onError, List? keys, }) { final context = useContext(); final QueryBowl queryBowl = QueryBowl.of(context); final ValueKey uKey = useMemoized(() => ValueKey(uuid.v4()), []); - final query = useRef( + final infiniteQuery = useRef( InfiniteQuery.fromOptions( job, externalData: externalData, @@ -31,46 +31,45 @@ InfiniteQuery final oldJob = usePrevious(job); final oldExternalData = usePrevious(externalData); - // final oldOnData = usePrevious(onData); - // final oldOnError = usePrevious(onError); + final oldOnData = usePrevious(onData); + final oldOnError = usePrevious(onError); final init = useCallback(([T? previousData]) { - query.value = queryBowl.addInfiniteQuery( + infiniteQuery.value = queryBowl.addInfiniteQuery( job, externalData: externalData, - // previousData: previousData, key: uKey, - // onData: onData, - // onError: onError, + onData: onData, + onError: onError, ); - final hasExternalDataChanged = query.value.externalData != null && - query.value.prevUsedExternalData != null && - !isShallowEqual( - query.value.externalData!, query.value.prevUsedExternalData!); - if (query.value.fetched && hasExternalDataChanged) { - query.value.refetch(); - } else if (!query.value.fetched) { - query.value.fetch(); + final hasExternalDataChanged = infiniteQuery.value.externalData != null && + infiniteQuery.value.prevUsedExternalData != null && + !isShallowEqual(infiniteQuery.value.externalData!, + infiniteQuery.value.prevUsedExternalData!); + if (infiniteQuery.value.fetched && hasExternalDataChanged) { + infiniteQuery.value.refetchPages(); + } else if (!infiniteQuery.value.fetched) { + infiniteQuery.value.fetch(); } }, [ queryBowl, - query.value, + infiniteQuery.value, uKey, job, externalData, - // onData, - // onError, + onData, + onError, ]); final disposeQuery = useCallback(() { - query.value.unmount(uKey); - // if (onData != null) query.value.removeDataListener(onData); - // if (onError != null) query.value.removeErrorListener(onError); + infiniteQuery.value.unmount(uKey); + if (onData != null) infiniteQuery.value.removeDataListener(onData); + if (onError != null) infiniteQuery.value.removeErrorListener(onError); }, [ - query.value, + infiniteQuery.value, uKey, - // onData, - // onError, + onData, + onError, ]); useEffect(() { @@ -79,8 +78,8 @@ InfiniteQuery }, []); useEffect(() { - // final hasOnErrorChanged = oldOnError != onError && oldOnError != null; - // final hasOnDataChanged = oldOnData != onData && oldOnData != null; + final hasOnErrorChanged = oldOnError != onError && oldOnError != null; + final hasOnDataChanged = oldOnData != onData && oldOnData != null; if (oldJob != null && oldJob.queryKey != job.queryKey) { disposeQuery(); init(); @@ -93,8 +92,8 @@ InfiniteQuery job, externalData: externalData, key: uKey, - // onData: onData, - // onError: onError, + onData: onData, + onError: onError, )..refetchPages(); } else { QueryBowl.of(context) @@ -102,22 +101,22 @@ InfiniteQuery ?.setExternalData(externalData); } - // if (hasOnDataChanged) query.value.removeDataListener(oldOnData); - // if (hasOnErrorChanged) query.value.removeErrorListener(oldOnError); + if (hasOnDataChanged) infiniteQuery.value.removeDataListener(oldOnData); + if (hasOnErrorChanged) + infiniteQuery.value.removeErrorListener(oldOnError); + } else { + if (hasOnDataChanged) { + infiniteQuery.value.removeDataListener(oldOnData); + if (onData != null) infiniteQuery.value.addDataListener(onData); + } + if (hasOnErrorChanged) { + infiniteQuery.value.removeErrorListener(oldOnError); + if (onError != null) infiniteQuery.value.addErrorListener(onError); + } } - // else { - // if (hasOnDataChanged) { - // query.value.removeDataListener(oldOnData); - // if (onData != null) query.value.addDataListener(onData); - // } - // if (hasOnErrorChanged) { - // query.value.removeErrorListener(oldOnError); - // if (onError != null) query.value.addErrorListener(onError); - // } - // } return null; }); return queryBowl.getInfiniteQuery(job.queryKey) ?? - query.value; + infiniteQuery.value; }