feat: new next_page signature, query/mutation fn in notifier and safe update in use_updater

This commit is contained in:
Kingkor Roy Tirtho
2023-02-26 13:51:40 +06:00
parent 1ea9370570
commit f2a23b085c
6 changed files with 62 additions and 70 deletions
@@ -61,12 +61,9 @@ class _InfiniteQueryPageWidgetState extends State<InfiniteQueryPageWidget> {
throw ClientException(res.statusCode.toString(), res.request?.url); throw ClientException(res.statusCode.toString(), res.request?.url);
} }
}, },
nextPage: (lastPage, pages) { nextPage: (lastPage, lastPageData) {
if (pages.isNotEmpty && /// returning [null] will set [hasNextPage] to [false]
pages.last.products.length < pages[lastPage].limit) { if (lastPageData.products.length < 10) return null;
/// returning [null] will set [hasNextPage] to [false]
return null;
}
return lastPage + 1; return lastPage + 1;
}, },
initialPage: 0, initialPage: 0,
@@ -13,7 +13,7 @@ typedef InfiniteQueryFn<DataType, PageType> = FutureOr<DataType?> Function(
PageType page); PageType page);
typedef InfiniteQueryNextPage<DataType, PageType> = PageType? Function( typedef InfiniteQueryNextPage<DataType, PageType> = PageType? Function(
PageType lastPage, PageType lastPage,
List<DataType> pages, DataType lastPageData,
); );
class InfiniteQueryPage<DataType, ErrorType, PageType> with Invalidation { class InfiniteQueryPage<DataType, ErrorType, PageType> with Invalidation {
@@ -59,32 +59,17 @@ class InfiniteQueryPage<DataType, ErrorType, PageType> with Invalidation {
class InfiniteQueryState<DataType, ErrorType, PageType> { class InfiniteQueryState<DataType, ErrorType, PageType> {
final Set<InfiniteQueryPage<DataType, ErrorType, PageType>> pages; final Set<InfiniteQueryPage<DataType, ErrorType, PageType>> pages;
final InfiniteQueryFn<DataType, PageType> queryFn;
final InfiniteQueryNextPage<DataType, PageType> _nextPage;
const InfiniteQueryState({ InfiniteQueryState({
required this.pages, required this.pages,
required this.queryFn, });
required InfiniteQueryNextPage<DataType, PageType> nextPage,
}) : _nextPage = nextPage;
PageType get lastPage => pages.last.page; PageType get lastPage => pages.last.page;
PageType? get getNextPage => _nextPage(
lastPage,
pages.map((e) => e.data).whereType<DataType>().toList(),
);
bool get hasNextPage => getNextPage != null; InfiniteQueryState<DataType, ErrorType, PageType> copyWith(
{Set<InfiniteQueryPage<DataType, ErrorType, PageType>>? pages}) {
InfiniteQueryState<DataType, ErrorType, PageType> copyWith({
Set<InfiniteQueryPage<DataType, ErrorType, PageType>>? pages,
InfiniteQueryFn<DataType, PageType>? queryFn,
InfiniteQueryNextPage<DataType, PageType>? nextPage,
}) {
return InfiniteQueryState<DataType, ErrorType, PageType>( return InfiniteQueryState<DataType, ErrorType, PageType>(
pages: pages ?? this.pages, pages: pages ?? this.pages,
queryFn: queryFn ?? this.queryFn,
nextPage: nextPage ?? this._nextPage,
); );
} }
} }
@@ -111,6 +96,9 @@ class InfiniteQuery<DataType, ErrorType, PageType>
final PageType _initialParam; final PageType _initialParam;
InfiniteQueryFn<DataType, PageType> _queryFn;
InfiniteQueryNextPage<DataType, PageType> _nextPage;
InfiniteQuery( InfiniteQuery(
this.key, this.key,
InfiniteQueryFn<DataType, PageType> queryFn, { InfiniteQueryFn<DataType, PageType> queryFn, {
@@ -123,6 +111,8 @@ class InfiniteQuery<DataType, ErrorType, PageType>
_dataController = StreamController.broadcast(), _dataController = StreamController.broadcast(),
_errorController = StreamController.broadcast(), _errorController = StreamController.broadcast(),
_box = Hive.lazyBox(QueryClient.infiniteQueryCachePrefix), _box = Hive.lazyBox(QueryClient.infiniteQueryCachePrefix),
_queryFn = queryFn,
_nextPage = nextPage,
super(InfiniteQueryState<DataType, ErrorType, PageType>( super(InfiniteQueryState<DataType, ErrorType, PageType>(
pages: { pages: {
InfiniteQueryPage<DataType, ErrorType, PageType>( InfiniteQueryPage<DataType, ErrorType, PageType>(
@@ -131,8 +121,6 @@ class InfiniteQuery<DataType, ErrorType, PageType>
staleDuration: refreshConfig.staleDuration, staleDuration: refreshConfig.staleDuration,
), ),
}, },
queryFn: queryFn,
nextPage: nextPage,
)) { )) {
if (jsonConfig != null) { if (jsonConfig != null) {
_mutex.protect(() async { _mutex.protect(() async {
@@ -191,6 +179,16 @@ class InfiniteQuery<DataType, ErrorType, PageType>
Stream<PageEvent<ErrorType, PageType>> get errorStream => Stream<PageEvent<ErrorType, PageType>> get errorStream =>
_errorController.stream; _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 isLoadingPage => !hasPageData && !hasPageError && _mutex.isLocked;
bool get isRefreshingPage => (hasPageData || hasPageError) && _mutex.isLocked; bool get isRefreshingPage => (hasPageData || hasPageError) && _mutex.isLocked;
bool get isInactive => !hasListeners; bool get isInactive => !hasListeners;
@@ -201,13 +199,13 @@ class InfiniteQuery<DataType, ErrorType, PageType>
bool get hasPageData => !hasPages ? false : state.pages.last.data != null; bool get hasPageData => !hasPages ? false : state.pages.last.data != null;
bool get hasPageError => !hasPages ? false : state.pages.last.error != null; bool get hasPageError => !hasPages ? false : state.pages.last.error != null;
bool get hasNextPage => state.hasNextPage; bool get hasNextPage => getNextPage != null;
Future<void> _operate(PageType page) { Future<void> _operate(PageType page) {
return _mutex.protect(() async { return _mutex.protect(() async {
state = state.copyWith(); state = state.copyWith();
_operation = cancellableRetryOperation( _operation = cancellableRetryOperation(
() => state.queryFn(page), () => _queryFn(page),
config: retryConfig, config: retryConfig,
onSuccessful: (data) async { onSuccessful: (data) async {
final dataPage = state.pages final dataPage = state.pages
@@ -219,7 +217,7 @@ class InfiniteQuery<DataType, ErrorType, PageType>
staleDuration: refreshConfig.staleDuration, staleDuration: refreshConfig.staleDuration,
), ),
) )
.copyWith(data: data); .copyWith(data: data, error: null);
state = state.copyWith( state = state.copyWith(
pages: {...state.pages..remove(dataPage), dataPage}, pages: {...state.pages..remove(dataPage), dataPage},
); );
@@ -288,7 +286,7 @@ class InfiniteQuery<DataType, ErrorType, PageType>
} }
Future<DataType?> fetchNext() async { Future<DataType?> fetchNext() async {
final nextPage = state.getNextPage; final nextPage = getNextPage;
if (_mutex.isLocked || nextPage == null) { if (_mutex.isLocked || nextPage == null) {
return state.pages.lastOrNull?.data; return state.pages.lastOrNull?.data;
} }
@@ -298,8 +296,8 @@ class InfiniteQuery<DataType, ErrorType, PageType>
} }
void updateQueryFn(InfiniteQueryFn<DataType, PageType> queryFn) { void updateQueryFn(InfiniteQueryFn<DataType, PageType> queryFn) {
if (state.queryFn == queryFn) return; if (_queryFn == queryFn) return;
state = state.copyWith(queryFn: queryFn); _queryFn = queryFn;
if (refreshConfig.refreshOnQueryFnChange) { if (refreshConfig.refreshOnQueryFnChange) {
refreshAll(); refreshAll();
} else { } else {
@@ -314,8 +312,8 @@ class InfiniteQuery<DataType, ErrorType, PageType>
} }
void updateNextPageFn(InfiniteQueryNextPage<DataType, PageType> nextPage) { void updateNextPageFn(InfiniteQueryNextPage<DataType, PageType> nextPage) {
if (state._nextPage == nextPage) return; if (_nextPage == nextPage) return;
state = state.copyWith(nextPage: nextPage); _nextPage = nextPage;
} }
void setPageData(PageType page, DataType data) { void setPageData(PageType page, DataType data) {
+9 -17
View File
@@ -14,11 +14,9 @@ typedef MutationFn<DataType, VariablesType> = Future<DataType> Function(
class MutationState<DataType, ErrorType, VariablesType> { class MutationState<DataType, ErrorType, VariablesType> {
final DataType? data; final DataType? data;
final ErrorType? error; final ErrorType? error;
final MutationFn<DataType, VariablesType> mutationFn;
final DateTime updatedAt; final DateTime updatedAt;
MutationState({ MutationState({
required this.mutationFn,
this.data, this.data,
this.error, this.error,
DateTime? updatedAt, DateTime? updatedAt,
@@ -28,10 +26,8 @@ class MutationState<DataType, ErrorType, VariablesType> {
DataType? data, DataType? data,
ErrorType? error, ErrorType? error,
DateTime? updatedAt, DateTime? updatedAt,
MutationFn<DataType, VariablesType>? mutationFn,
}) { }) {
return MutationState<DataType, ErrorType, VariablesType>( return MutationState<DataType, ErrorType, VariablesType>(
mutationFn: mutationFn ?? this.mutationFn,
data: data ?? this.data, data: data ?? this.data,
error: error ?? this.error, error: error ?? this.error,
updatedAt: updatedAt ?? DateTime.now(), updatedAt: updatedAt ?? DateTime.now(),
@@ -43,22 +39,20 @@ class Mutation<DataType, ErrorType, VariablesType>
extends StateNotifier<MutationState<DataType, ErrorType, VariablesType>> extends StateNotifier<MutationState<DataType, ErrorType, VariablesType>>
with Retryer<DataType, ErrorType> { with Retryer<DataType, ErrorType> {
final String key; final String key;
final MutationFn<DataType, VariablesType> mutationFn;
final RetryConfig retryConfig; final RetryConfig retryConfig;
MutationFn<DataType, VariablesType> _mutationFn;
Mutation( Mutation(
this.key, this.key,
this.mutationFn, { MutationFn<DataType, VariablesType> mutationFn, {
this.retryConfig = DefaultConstants.retryConfig, this.retryConfig = DefaultConstants.retryConfig,
}) : _dataController = StreamController.broadcast(), }) : _dataController = StreamController.broadcast(),
_errorController = StreamController.broadcast(), _errorController = StreamController.broadcast(),
_mutationController = StreamController.broadcast(), _mutationController = StreamController.broadcast(),
super( _mutationFn = mutationFn,
MutationState<DataType, ErrorType, VariablesType>( super(MutationState<DataType, ErrorType, VariablesType>());
mutationFn: mutationFn,
),
);
bool get isInactive => !hasListeners; bool get isInactive => !hasListeners;
bool get isMutating => _mutex.isLocked; bool get isMutating => _mutex.isLocked;
@@ -83,7 +77,7 @@ class Mutation<DataType, ErrorType, VariablesType>
_operation = await cancellableRetryOperation( _operation = await cancellableRetryOperation(
() { () {
_mutationController.add(variables); _mutationController.add(variables);
return state.mutationFn(variables); return _mutationFn(variables);
}, },
config: retryConfig, config: retryConfig,
onSuccessful: (data) { onSuccessful: (data) {
@@ -113,15 +107,13 @@ class Mutation<DataType, ErrorType, VariablesType>
} }
void updateMutationFn(MutationFn<DataType, VariablesType> mutationFn) { void updateMutationFn(MutationFn<DataType, VariablesType> mutationFn) {
if (mutationFn == state.mutationFn) return; if (mutationFn == _mutationFn) return;
state = state.copyWith(mutationFn: mutationFn, updatedAt: state.updatedAt); _mutationFn = mutationFn;
} }
Future<void> reset() async { Future<void> reset() async {
await _operation?.cancel(); await _operation?.cancel();
state = MutationState<DataType, ErrorType, VariablesType>( state = MutationState<DataType, ErrorType, VariablesType>();
mutationFn: state.mutationFn,
);
} }
@override @override
+7 -9
View File
@@ -17,15 +17,12 @@ typedef QueryFn<DataType> = FutureOr<DataType?> Function();
class QueryState<DataType, ErrorType> with Invalidation { class QueryState<DataType, ErrorType> with Invalidation {
final DataType? data; final DataType? data;
final ErrorType? error; final ErrorType? error;
final QueryFn<DataType> queryFn;
final DateTime updatedAt; final DateTime updatedAt;
final Duration staleDuration; final Duration staleDuration;
const QueryState({ const QueryState({
this.data, this.data,
this.error, this.error,
required this.queryFn,
required this.updatedAt, required this.updatedAt,
required this.staleDuration, required this.staleDuration,
}); });
@@ -34,14 +31,12 @@ class QueryState<DataType, ErrorType> with Invalidation {
DataType? data, DataType? data,
ErrorType? error, ErrorType? error,
DateTime? updatedAt, DateTime? updatedAt,
QueryFn<DataType>? queryFn,
}) { }) {
return QueryState<DataType, ErrorType>( return QueryState<DataType, ErrorType>(
updatedAt: updatedAt ?? this.updatedAt, updatedAt: updatedAt ?? this.updatedAt,
staleDuration: staleDuration, staleDuration: staleDuration,
data: data ?? this.data, data: data ?? this.data,
error: error ?? this.error, error: error ?? this.error,
queryFn: queryFn ?? this.queryFn,
); );
} }
} }
@@ -55,6 +50,8 @@ class Query<DataType, ErrorType>
final RetryConfig retryConfig; final RetryConfig retryConfig;
final JsonConfig<DataType>? jsonConfig; final JsonConfig<DataType>? jsonConfig;
QueryFn<DataType> _queryFn;
Query( Query(
this.key, this.key,
QueryFn<DataType> queryFn, { QueryFn<DataType> queryFn, {
@@ -66,11 +63,11 @@ class Query<DataType, ErrorType>
_dataController = StreamController<DataType>.broadcast(), _dataController = StreamController<DataType>.broadcast(),
_errorController = StreamController<ErrorType>.broadcast(), _errorController = StreamController<ErrorType>.broadcast(),
_initial = initial, _initial = initial,
_queryFn = queryFn,
super(QueryState<DataType, ErrorType>( super(QueryState<DataType, ErrorType>(
updatedAt: DateTime.now(), updatedAt: DateTime.now(),
staleDuration: refreshConfig.staleDuration, staleDuration: refreshConfig.staleDuration,
data: initial, data: initial,
queryFn: queryFn,
)) { )) {
if (jsonConfig != null) { if (jsonConfig != null) {
_mutex.protect(() async { _mutex.protect(() async {
@@ -123,11 +120,12 @@ class Query<DataType, ErrorType>
return _mutex.protect(() async { return _mutex.protect(() async {
state = state.copyWith(); state = state.copyWith();
_operation = cancellableRetryOperation( _operation = cancellableRetryOperation(
state.queryFn, _queryFn,
config: retryConfig, config: retryConfig,
onSuccessful: (DataType? data) { onSuccessful: (DataType? data) {
state = state.copyWith( state = state.copyWith(
data: data, data: data,
error: null,
updatedAt: DateTime.now(), updatedAt: DateTime.now(),
); );
if (data is DataType) { if (data is DataType) {
@@ -160,8 +158,8 @@ class Query<DataType, ErrorType>
} }
void updateQueryFn(QueryFn<DataType> queryFn) { void updateQueryFn(QueryFn<DataType> queryFn) {
if (state.queryFn == queryFn) return; if (_queryFn == queryFn) return;
state = state.copyWith(queryFn: queryFn); _queryFn = queryFn;
if (state.isStale || refreshConfig.refreshOnQueryFnChange) { if (state.isStale || refreshConfig.refreshOnQueryFnChange) {
refresh(); refresh();
} }
@@ -26,12 +26,9 @@ class InfiniteQueryPageWidget extends HookWidget {
throw ClientException(res.statusCode.toString(), res.request?.url); throw ClientException(res.statusCode.toString(), res.request?.url);
} }
}, },
nextPage: (lastPage, pages) { nextPage: (lastPage, lastPageData) {
if (pages.isNotEmpty && /// returning [null] will set [hasNextPage] to [false]
pages.last.products.length < pages[lastPage].limit) { if (lastPageData.products.length < 10) return null;
/// returning [null] will set [hasNextPage] to [false]
return null;
}
return lastPage + 1; return lastPage + 1;
}, },
initialPage: 0, initialPage: 0,
@@ -1,10 +1,20 @@
import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
ValueChanged<dynamic> useUpdater() { ValueChanged<dynamic> useUpdater() {
final state = useState(false); final state = useState(false);
final isMounted = useIsMounted(); final isMounted = useIsMounted();
return ([_]) { return ([_]) async {
if (isMounted()) state.value = !state.value; 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;
}; };
} }