feat: new next_page signature, query/mutation fn in notifier and safe update in use_updater
This commit is contained in:
@@ -61,12 +61,9 @@ class _InfiniteQueryPageWidgetState extends State<InfiniteQueryPageWidget> {
|
||||
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,
|
||||
|
||||
@@ -13,7 +13,7 @@ typedef InfiniteQueryFn<DataType, PageType> = FutureOr<DataType?> Function(
|
||||
PageType page);
|
||||
typedef InfiniteQueryNextPage<DataType, PageType> = PageType? Function(
|
||||
PageType lastPage,
|
||||
List<DataType> pages,
|
||||
DataType lastPageData,
|
||||
);
|
||||
|
||||
class InfiniteQueryPage<DataType, ErrorType, PageType> with Invalidation {
|
||||
@@ -59,32 +59,17 @@ class InfiniteQueryPage<DataType, ErrorType, PageType> with Invalidation {
|
||||
|
||||
class InfiniteQueryState<DataType, ErrorType, PageType> {
|
||||
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.queryFn,
|
||||
required InfiniteQueryNextPage<DataType, PageType> nextPage,
|
||||
}) : _nextPage = nextPage;
|
||||
});
|
||||
|
||||
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,
|
||||
InfiniteQueryFn<DataType, PageType>? queryFn,
|
||||
InfiniteQueryNextPage<DataType, PageType>? nextPage,
|
||||
}) {
|
||||
InfiniteQueryState<DataType, ErrorType, PageType> copyWith(
|
||||
{Set<InfiniteQueryPage<DataType, ErrorType, PageType>>? pages}) {
|
||||
return InfiniteQueryState<DataType, ErrorType, PageType>(
|
||||
pages: pages ?? this.pages,
|
||||
queryFn: queryFn ?? this.queryFn,
|
||||
nextPage: nextPage ?? this._nextPage,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -111,6 +96,9 @@ class InfiniteQuery<DataType, ErrorType, PageType>
|
||||
|
||||
final PageType _initialParam;
|
||||
|
||||
InfiniteQueryFn<DataType, PageType> _queryFn;
|
||||
InfiniteQueryNextPage<DataType, PageType> _nextPage;
|
||||
|
||||
InfiniteQuery(
|
||||
this.key,
|
||||
InfiniteQueryFn<DataType, PageType> queryFn, {
|
||||
@@ -123,6 +111,8 @@ class InfiniteQuery<DataType, ErrorType, PageType>
|
||||
_dataController = StreamController.broadcast(),
|
||||
_errorController = StreamController.broadcast(),
|
||||
_box = Hive.lazyBox(QueryClient.infiniteQueryCachePrefix),
|
||||
_queryFn = queryFn,
|
||||
_nextPage = nextPage,
|
||||
super(InfiniteQueryState<DataType, ErrorType, PageType>(
|
||||
pages: {
|
||||
InfiniteQueryPage<DataType, ErrorType, PageType>(
|
||||
@@ -131,8 +121,6 @@ class InfiniteQuery<DataType, ErrorType, PageType>
|
||||
staleDuration: refreshConfig.staleDuration,
|
||||
),
|
||||
},
|
||||
queryFn: queryFn,
|
||||
nextPage: nextPage,
|
||||
)) {
|
||||
if (jsonConfig != null) {
|
||||
_mutex.protect(() async {
|
||||
@@ -191,6 +179,16 @@ class InfiniteQuery<DataType, ErrorType, PageType>
|
||||
Stream<PageEvent<ErrorType, PageType>> 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<DataType, ErrorType, PageType>
|
||||
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<void> _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<DataType, ErrorType, PageType>
|
||||
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<DataType, ErrorType, PageType>
|
||||
}
|
||||
|
||||
Future<DataType?> 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<DataType, ErrorType, PageType>
|
||||
}
|
||||
|
||||
void updateQueryFn(InfiniteQueryFn<DataType, PageType> 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<DataType, ErrorType, PageType>
|
||||
}
|
||||
|
||||
void updateNextPageFn(InfiniteQueryNextPage<DataType, PageType> nextPage) {
|
||||
if (state._nextPage == nextPage) return;
|
||||
state = state.copyWith(nextPage: nextPage);
|
||||
if (_nextPage == nextPage) return;
|
||||
_nextPage = nextPage;
|
||||
}
|
||||
|
||||
void setPageData(PageType page, DataType data) {
|
||||
|
||||
@@ -14,11 +14,9 @@ typedef MutationFn<DataType, VariablesType> = Future<DataType> Function(
|
||||
class MutationState<DataType, ErrorType, VariablesType> {
|
||||
final DataType? data;
|
||||
final ErrorType? error;
|
||||
final MutationFn<DataType, VariablesType> mutationFn;
|
||||
final DateTime updatedAt;
|
||||
|
||||
MutationState({
|
||||
required this.mutationFn,
|
||||
this.data,
|
||||
this.error,
|
||||
DateTime? updatedAt,
|
||||
@@ -28,10 +26,8 @@ class MutationState<DataType, ErrorType, VariablesType> {
|
||||
DataType? data,
|
||||
ErrorType? error,
|
||||
DateTime? updatedAt,
|
||||
MutationFn<DataType, VariablesType>? mutationFn,
|
||||
}) {
|
||||
return MutationState<DataType, ErrorType, VariablesType>(
|
||||
mutationFn: mutationFn ?? this.mutationFn,
|
||||
data: data ?? this.data,
|
||||
error: error ?? this.error,
|
||||
updatedAt: updatedAt ?? DateTime.now(),
|
||||
@@ -43,22 +39,20 @@ class Mutation<DataType, ErrorType, VariablesType>
|
||||
extends StateNotifier<MutationState<DataType, ErrorType, VariablesType>>
|
||||
with Retryer<DataType, ErrorType> {
|
||||
final String key;
|
||||
final MutationFn<DataType, VariablesType> mutationFn;
|
||||
|
||||
final RetryConfig retryConfig;
|
||||
|
||||
MutationFn<DataType, VariablesType> _mutationFn;
|
||||
|
||||
Mutation(
|
||||
this.key,
|
||||
this.mutationFn, {
|
||||
MutationFn<DataType, VariablesType> mutationFn, {
|
||||
this.retryConfig = DefaultConstants.retryConfig,
|
||||
}) : _dataController = StreamController.broadcast(),
|
||||
_errorController = StreamController.broadcast(),
|
||||
_mutationController = StreamController.broadcast(),
|
||||
super(
|
||||
MutationState<DataType, ErrorType, VariablesType>(
|
||||
mutationFn: mutationFn,
|
||||
),
|
||||
);
|
||||
_mutationFn = mutationFn,
|
||||
super(MutationState<DataType, ErrorType, VariablesType>());
|
||||
|
||||
bool get isInactive => !hasListeners;
|
||||
bool get isMutating => _mutex.isLocked;
|
||||
@@ -83,7 +77,7 @@ class Mutation<DataType, ErrorType, VariablesType>
|
||||
_operation = await cancellableRetryOperation(
|
||||
() {
|
||||
_mutationController.add(variables);
|
||||
return state.mutationFn(variables);
|
||||
return _mutationFn(variables);
|
||||
},
|
||||
config: retryConfig,
|
||||
onSuccessful: (data) {
|
||||
@@ -113,15 +107,13 @@ class Mutation<DataType, ErrorType, VariablesType>
|
||||
}
|
||||
|
||||
void updateMutationFn(MutationFn<DataType, VariablesType> mutationFn) {
|
||||
if (mutationFn == state.mutationFn) return;
|
||||
state = state.copyWith(mutationFn: mutationFn, updatedAt: state.updatedAt);
|
||||
if (mutationFn == _mutationFn) return;
|
||||
_mutationFn = mutationFn;
|
||||
}
|
||||
|
||||
Future<void> reset() async {
|
||||
await _operation?.cancel();
|
||||
state = MutationState<DataType, ErrorType, VariablesType>(
|
||||
mutationFn: state.mutationFn,
|
||||
);
|
||||
state = MutationState<DataType, ErrorType, VariablesType>();
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -17,15 +17,12 @@ typedef QueryFn<DataType> = FutureOr<DataType?> Function();
|
||||
class QueryState<DataType, ErrorType> with Invalidation {
|
||||
final DataType? data;
|
||||
final ErrorType? error;
|
||||
final QueryFn<DataType> 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<DataType, ErrorType> with Invalidation {
|
||||
DataType? data,
|
||||
ErrorType? error,
|
||||
DateTime? updatedAt,
|
||||
QueryFn<DataType>? queryFn,
|
||||
}) {
|
||||
return QueryState<DataType, ErrorType>(
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
staleDuration: staleDuration,
|
||||
data: data ?? this.data,
|
||||
error: error ?? this.error,
|
||||
queryFn: queryFn ?? this.queryFn,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -55,6 +50,8 @@ class Query<DataType, ErrorType>
|
||||
final RetryConfig retryConfig;
|
||||
final JsonConfig<DataType>? jsonConfig;
|
||||
|
||||
QueryFn<DataType> _queryFn;
|
||||
|
||||
Query(
|
||||
this.key,
|
||||
QueryFn<DataType> queryFn, {
|
||||
@@ -66,11 +63,11 @@ class Query<DataType, ErrorType>
|
||||
_dataController = StreamController<DataType>.broadcast(),
|
||||
_errorController = StreamController<ErrorType>.broadcast(),
|
||||
_initial = initial,
|
||||
_queryFn = queryFn,
|
||||
super(QueryState<DataType, ErrorType>(
|
||||
updatedAt: DateTime.now(),
|
||||
staleDuration: refreshConfig.staleDuration,
|
||||
data: initial,
|
||||
queryFn: queryFn,
|
||||
)) {
|
||||
if (jsonConfig != null) {
|
||||
_mutex.protect(() async {
|
||||
@@ -123,11 +120,12 @@ class Query<DataType, ErrorType>
|
||||
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<DataType, ErrorType>
|
||||
}
|
||||
|
||||
void updateQueryFn(QueryFn<DataType> queryFn) {
|
||||
if (state.queryFn == queryFn) return;
|
||||
state = state.copyWith(queryFn: queryFn);
|
||||
if (_queryFn == queryFn) return;
|
||||
_queryFn = queryFn;
|
||||
if (state.isStale || refreshConfig.refreshOnQueryFnChange) {
|
||||
refresh();
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
|
||||
ValueChanged<dynamic> 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;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user