From f2a23b085cd657a1612d87749f6592b4d67814c5 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sun, 26 Feb 2023 13:51:40 +0600 Subject: [PATCH] feat: new next_page signature, query/mutation fn in notifier and safe update in use_updater --- .../example/lib/pages/infinite_query.dart | 9 +-- .../fl_query/lib/src/core/infinite_query.dart | 58 +++++++++---------- packages/fl_query/lib/src/core/mutation.dart | 26 +++------ packages/fl_query/lib/src/core/query.dart | 16 +++-- .../example/lib/pages/infinite_query.dart | 9 +-- .../lib/src/utils/use_updater.dart | 14 ++++- 6 files changed, 62 insertions(+), 70 deletions(-) diff --git a/packages/fl_query/example/lib/pages/infinite_query.dart b/packages/fl_query/example/lib/pages/infinite_query.dart index 151c5c6..171d44b 100644 --- a/packages/fl_query/example/lib/pages/infinite_query.dart +++ b/packages/fl_query/example/lib/pages/infinite_query.dart @@ -61,12 +61,9 @@ class _InfiniteQueryPageWidgetState extends State { throw ClientException(res.statusCode.toString(), res.request?.url); } }, - nextPage: (lastPage, pages) { - if (pages.isNotEmpty && - pages.last.products.length < pages[lastPage].limit) { - /// returning [null] will set [hasNextPage] to [false] - return null; - } + nextPage: (lastPage, lastPageData) { + /// returning [null] will set [hasNextPage] to [false] + if (lastPageData.products.length < 10) return null; return lastPage + 1; }, initialPage: 0, diff --git a/packages/fl_query/lib/src/core/infinite_query.dart b/packages/fl_query/lib/src/core/infinite_query.dart index cdc83a2..b94b147 100644 --- a/packages/fl_query/lib/src/core/infinite_query.dart +++ b/packages/fl_query/lib/src/core/infinite_query.dart @@ -13,7 +13,7 @@ typedef InfiniteQueryFn = FutureOr Function( PageType page); typedef InfiniteQueryNextPage = PageType? Function( PageType lastPage, - List pages, + DataType lastPageData, ); class InfiniteQueryPage with Invalidation { @@ -59,32 +59,17 @@ class InfiniteQueryPage with Invalidation { class InfiniteQueryState { final Set> pages; - final InfiniteQueryFn queryFn; - final InfiniteQueryNextPage _nextPage; - const InfiniteQueryState({ + InfiniteQueryState({ required this.pages, - required this.queryFn, - required InfiniteQueryNextPage nextPage, - }) : _nextPage = nextPage; + }); PageType get lastPage => pages.last.page; - PageType? get getNextPage => _nextPage( - lastPage, - pages.map((e) => e.data).whereType().toList(), - ); - bool get hasNextPage => getNextPage != null; - - InfiniteQueryState copyWith({ - Set>? pages, - InfiniteQueryFn? queryFn, - InfiniteQueryNextPage? nextPage, - }) { + InfiniteQueryState copyWith( + {Set>? pages}) { return InfiniteQueryState( pages: pages ?? this.pages, - queryFn: queryFn ?? this.queryFn, - nextPage: nextPage ?? this._nextPage, ); } } @@ -111,6 +96,9 @@ class InfiniteQuery final PageType _initialParam; + InfiniteQueryFn _queryFn; + InfiniteQueryNextPage _nextPage; + InfiniteQuery( this.key, InfiniteQueryFn queryFn, { @@ -123,6 +111,8 @@ class InfiniteQuery _dataController = StreamController.broadcast(), _errorController = StreamController.broadcast(), _box = Hive.lazyBox(QueryClient.infiniteQueryCachePrefix), + _queryFn = queryFn, + _nextPage = nextPage, super(InfiniteQueryState( pages: { InfiniteQueryPage( @@ -131,8 +121,6 @@ class InfiniteQuery staleDuration: refreshConfig.staleDuration, ), }, - queryFn: queryFn, - nextPage: nextPage, )) { if (jsonConfig != null) { _mutex.protect(() async { @@ -191,6 +179,16 @@ class InfiniteQuery Stream> get errorStream => _errorController.stream; + PageType? get getNextPage { + final lastPageData = state.pages + .firstWhereOrNull((e) => e.data is DataType && e.page == lastPage) + ?.data; + + if (lastPageData == null) return null; + + return _nextPage(lastPage, lastPageData); + } + bool get isLoadingPage => !hasPageData && !hasPageError && _mutex.isLocked; bool get isRefreshingPage => (hasPageData || hasPageError) && _mutex.isLocked; bool get isInactive => !hasListeners; @@ -201,13 +199,13 @@ class InfiniteQuery bool get hasPageData => !hasPages ? false : state.pages.last.data != null; bool get hasPageError => !hasPages ? false : state.pages.last.error != null; - bool get hasNextPage => state.hasNextPage; + bool get hasNextPage => getNextPage != null; Future _operate(PageType page) { return _mutex.protect(() async { state = state.copyWith(); _operation = cancellableRetryOperation( - () => state.queryFn(page), + () => _queryFn(page), config: retryConfig, onSuccessful: (data) async { final dataPage = state.pages @@ -219,7 +217,7 @@ class InfiniteQuery staleDuration: refreshConfig.staleDuration, ), ) - .copyWith(data: data); + .copyWith(data: data, error: null); state = state.copyWith( pages: {...state.pages..remove(dataPage), dataPage}, ); @@ -288,7 +286,7 @@ class InfiniteQuery } Future fetchNext() async { - final nextPage = state.getNextPage; + final nextPage = getNextPage; if (_mutex.isLocked || nextPage == null) { return state.pages.lastOrNull?.data; } @@ -298,8 +296,8 @@ class InfiniteQuery } void updateQueryFn(InfiniteQueryFn queryFn) { - if (state.queryFn == queryFn) return; - state = state.copyWith(queryFn: queryFn); + if (_queryFn == queryFn) return; + _queryFn = queryFn; if (refreshConfig.refreshOnQueryFnChange) { refreshAll(); } else { @@ -314,8 +312,8 @@ class InfiniteQuery } void updateNextPageFn(InfiniteQueryNextPage nextPage) { - if (state._nextPage == nextPage) return; - state = state.copyWith(nextPage: nextPage); + if (_nextPage == nextPage) return; + _nextPage = nextPage; } void setPageData(PageType page, DataType data) { diff --git a/packages/fl_query/lib/src/core/mutation.dart b/packages/fl_query/lib/src/core/mutation.dart index c3151f1..7a7e5fd 100644 --- a/packages/fl_query/lib/src/core/mutation.dart +++ b/packages/fl_query/lib/src/core/mutation.dart @@ -14,11 +14,9 @@ typedef MutationFn = Future Function( class MutationState { final DataType? data; final ErrorType? error; - final MutationFn mutationFn; final DateTime updatedAt; MutationState({ - required this.mutationFn, this.data, this.error, DateTime? updatedAt, @@ -28,10 +26,8 @@ class MutationState { DataType? data, ErrorType? error, DateTime? updatedAt, - MutationFn? mutationFn, }) { return MutationState( - mutationFn: mutationFn ?? this.mutationFn, data: data ?? this.data, error: error ?? this.error, updatedAt: updatedAt ?? DateTime.now(), @@ -43,22 +39,20 @@ class Mutation extends StateNotifier> with Retryer { final String key; - final MutationFn mutationFn; final RetryConfig retryConfig; + MutationFn _mutationFn; + Mutation( this.key, - this.mutationFn, { + MutationFn mutationFn, { this.retryConfig = DefaultConstants.retryConfig, }) : _dataController = StreamController.broadcast(), _errorController = StreamController.broadcast(), _mutationController = StreamController.broadcast(), - super( - MutationState( - mutationFn: mutationFn, - ), - ); + _mutationFn = mutationFn, + super(MutationState()); bool get isInactive => !hasListeners; bool get isMutating => _mutex.isLocked; @@ -83,7 +77,7 @@ class Mutation _operation = await cancellableRetryOperation( () { _mutationController.add(variables); - return state.mutationFn(variables); + return _mutationFn(variables); }, config: retryConfig, onSuccessful: (data) { @@ -113,15 +107,13 @@ class Mutation } void updateMutationFn(MutationFn mutationFn) { - if (mutationFn == state.mutationFn) return; - state = state.copyWith(mutationFn: mutationFn, updatedAt: state.updatedAt); + if (mutationFn == _mutationFn) return; + _mutationFn = mutationFn; } Future reset() async { await _operation?.cancel(); - state = MutationState( - mutationFn: state.mutationFn, - ); + state = MutationState(); } @override diff --git a/packages/fl_query/lib/src/core/query.dart b/packages/fl_query/lib/src/core/query.dart index a8427a9..da4e3cf 100644 --- a/packages/fl_query/lib/src/core/query.dart +++ b/packages/fl_query/lib/src/core/query.dart @@ -17,15 +17,12 @@ typedef QueryFn = FutureOr Function(); class QueryState with Invalidation { final DataType? data; final ErrorType? error; - final QueryFn queryFn; - final DateTime updatedAt; final Duration staleDuration; const QueryState({ this.data, this.error, - required this.queryFn, required this.updatedAt, required this.staleDuration, }); @@ -34,14 +31,12 @@ class QueryState with Invalidation { DataType? data, ErrorType? error, DateTime? updatedAt, - QueryFn? queryFn, }) { return QueryState( updatedAt: updatedAt ?? this.updatedAt, staleDuration: staleDuration, data: data ?? this.data, error: error ?? this.error, - queryFn: queryFn ?? this.queryFn, ); } } @@ -55,6 +50,8 @@ class Query final RetryConfig retryConfig; final JsonConfig? jsonConfig; + QueryFn _queryFn; + Query( this.key, QueryFn queryFn, { @@ -66,11 +63,11 @@ class Query _dataController = StreamController.broadcast(), _errorController = StreamController.broadcast(), _initial = initial, + _queryFn = queryFn, super(QueryState( updatedAt: DateTime.now(), staleDuration: refreshConfig.staleDuration, data: initial, - queryFn: queryFn, )) { if (jsonConfig != null) { _mutex.protect(() async { @@ -123,11 +120,12 @@ class Query return _mutex.protect(() async { state = state.copyWith(); _operation = cancellableRetryOperation( - state.queryFn, + _queryFn, config: retryConfig, onSuccessful: (DataType? data) { state = state.copyWith( data: data, + error: null, updatedAt: DateTime.now(), ); if (data is DataType) { @@ -160,8 +158,8 @@ class Query } void updateQueryFn(QueryFn queryFn) { - if (state.queryFn == queryFn) return; - state = state.copyWith(queryFn: queryFn); + if (_queryFn == queryFn) return; + _queryFn = queryFn; if (state.isStale || refreshConfig.refreshOnQueryFnChange) { refresh(); } diff --git a/packages/fl_query_hooks/example/lib/pages/infinite_query.dart b/packages/fl_query_hooks/example/lib/pages/infinite_query.dart index c75f936..3916f2a 100644 --- a/packages/fl_query_hooks/example/lib/pages/infinite_query.dart +++ b/packages/fl_query_hooks/example/lib/pages/infinite_query.dart @@ -26,12 +26,9 @@ class InfiniteQueryPageWidget extends HookWidget { throw ClientException(res.statusCode.toString(), res.request?.url); } }, - nextPage: (lastPage, pages) { - if (pages.isNotEmpty && - pages.last.products.length < pages[lastPage].limit) { - /// returning [null] will set [hasNextPage] to [false] - return null; - } + nextPage: (lastPage, lastPageData) { + /// returning [null] will set [hasNextPage] to [false] + if (lastPageData.products.length < 10) return null; return lastPage + 1; }, initialPage: 0, diff --git a/packages/fl_query_hooks/lib/src/utils/use_updater.dart b/packages/fl_query_hooks/lib/src/utils/use_updater.dart index bd33f0d..0a16aca 100644 --- a/packages/fl_query_hooks/lib/src/utils/use_updater.dart +++ b/packages/fl_query_hooks/lib/src/utils/use_updater.dart @@ -1,10 +1,20 @@ +import 'package:flutter/scheduler.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; ValueChanged useUpdater() { final state = useState(false); final isMounted = useIsMounted(); - return ([_]) { - if (isMounted()) state.value = !state.value; + return ([_]) async { + if (!isMounted()) return; + + // if there's a current frame, + if (SchedulerBinding.instance.schedulerPhase != SchedulerPhase.idle) { + // wait for the end of that frame. + await SchedulerBinding.instance.endOfFrame; + if (!isMounted()) return; + } + + state.value = !state.value; }; }