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);
}
},
nextPage: (lastPage, pages) {
if (pages.isNotEmpty &&
pages.last.products.length < pages[lastPage].limit) {
nextPage: (lastPage, lastPageData) {
/// returning [null] will set [hasNextPage] to [false]
return null;
}
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) {
+9 -17
View File
@@ -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
+7 -9
View File
@@ -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) {
nextPage: (lastPage, lastPageData) {
/// returning [null] will set [hasNextPage] to [false]
return null;
}
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;
};
}