feat: add infinite query builder with example
fix(query): updateQueryFn refetch when stale and update state of builders when widget is mounted
This commit is contained in:
@@ -8,9 +8,9 @@ abstract class DefaultConstants {
|
||||
);
|
||||
|
||||
static const RefreshConfig refreshConfig = RefreshConfig(
|
||||
staleDuration: Duration(seconds: 10),
|
||||
staleDuration: Duration(minutes: 2, milliseconds: 250),
|
||||
refreshInterval: Duration.zero,
|
||||
refreshOnMount: true,
|
||||
refreshOnMount: false,
|
||||
refreshOnQueryFnChange: false,
|
||||
);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:fl_query/src/collections/json_config.dart';
|
||||
import 'package:fl_query/src/collections/refresh_config.dart';
|
||||
import 'package:fl_query/src/collections/retry_config.dart';
|
||||
import 'package:fl_query/src/core/cache.dart';
|
||||
import 'package:fl_query/src/core/infinite_query.dart';
|
||||
import 'package:fl_query/src/core/provider.dart';
|
||||
import 'package:fl_query/src/core/query.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -83,6 +84,98 @@ class QueryClient {
|
||||
return await Future.wait(queries.map((query) => query.refresh()));
|
||||
}
|
||||
|
||||
InfiniteQuery<DataType, ErrorType, KeyType, PageType>
|
||||
createInfiniteQuery<DataType, ErrorType, KeyType, PageType>(
|
||||
ValueKey<KeyType> key,
|
||||
InfiniteQueryFn<DataType, PageType> queryFn, {
|
||||
required InfiniteQueryNextPage<DataType, PageType> nextPage,
|
||||
required PageType initialParam,
|
||||
RetryConfig retryConfig = DefaultConstants.retryConfig,
|
||||
RefreshConfig refreshConfig = DefaultConstants.refreshConfig,
|
||||
JsonConfig<DataType>? jsonConfig,
|
||||
}) {
|
||||
final query = cache.infiniteQueries
|
||||
.firstWhere(
|
||||
(query) => query.key == key,
|
||||
orElse: () => InfiniteQuery<DataType, ErrorType, KeyType, PageType>(
|
||||
key,
|
||||
queryFn,
|
||||
nextPage: nextPage,
|
||||
initialParam: initialParam,
|
||||
retryConfig: retryConfig,
|
||||
refreshConfig: refreshConfig,
|
||||
jsonConfig: jsonConfig,
|
||||
),
|
||||
)
|
||||
.cast<DataType, ErrorType, KeyType, PageType>();
|
||||
query.updateQueryFn(queryFn);
|
||||
cache.addInfiniteQuery(query);
|
||||
return query;
|
||||
}
|
||||
|
||||
Future<DataType?> fetchInfiniteQuery<DataType, ErrorType, KeyType, PageType>(
|
||||
ValueKey<KeyType> key, InfiniteQueryFn<DataType, PageType> queryFn,
|
||||
{required InfiniteQueryNextPage<DataType, PageType> nextPage,
|
||||
required PageType initialParam,
|
||||
RetryConfig retryConfig = DefaultConstants.retryConfig,
|
||||
RefreshConfig refreshConfig = DefaultConstants.refreshConfig,
|
||||
JsonConfig<DataType>? jsonConfig}) async {
|
||||
final query = createInfiniteQuery<DataType, ErrorType, KeyType, PageType>(
|
||||
key,
|
||||
queryFn,
|
||||
nextPage: nextPage,
|
||||
initialParam: initialParam,
|
||||
retryConfig: retryConfig,
|
||||
refreshConfig: refreshConfig,
|
||||
jsonConfig: jsonConfig,
|
||||
);
|
||||
return await query.fetch();
|
||||
}
|
||||
|
||||
InfiniteQuery<DataType, ErrorType, KeyType, PageType>?
|
||||
getInfiniteQuery<DataType, ErrorType, KeyType, PageType>(
|
||||
ValueKey<KeyType> key) {
|
||||
return cache.infiniteQueries
|
||||
.firstWhereOrNull((query) => query.key == key)
|
||||
?.cast<DataType, ErrorType, KeyType, PageType>();
|
||||
}
|
||||
|
||||
List<InfiniteQuery> getInfiniteQueries(List<ValueKey> keys) {
|
||||
return cache.infiniteQueries
|
||||
.where((query) => keys.contains(query.key))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<DataType?>
|
||||
refreshInfiniteQuery<DataType, ErrorType, KeyType, PageType>(
|
||||
ValueKey<KeyType> key,
|
||||
[PageType? page]) async {
|
||||
final query = getInfiniteQuery<DataType, ErrorType, KeyType, PageType>(key);
|
||||
if (query == null) return null;
|
||||
return await query.refresh(page);
|
||||
}
|
||||
|
||||
Future<List<DataType>?>
|
||||
refreshInfiniteQueryAllPages<DataType, ErrorType, KeyType, PageType>(
|
||||
ValueKey<KeyType> key) async {
|
||||
final query = getInfiniteQuery<DataType, ErrorType, KeyType, PageType>(key);
|
||||
if (query == null) return [];
|
||||
return await query.refreshAll();
|
||||
}
|
||||
|
||||
Future<List> refreshInfiniteQueries(List<ValueKey> keys) async {
|
||||
final queries = getInfiniteQueries(keys);
|
||||
return await Future.wait(queries.map((query) => query.refresh()));
|
||||
}
|
||||
|
||||
Future<Map<ValueKey, List?>> refreshInfiniteQueriesAllPages(
|
||||
List<ValueKey> keys) async {
|
||||
final queries = getInfiniteQueries(keys);
|
||||
return await Future.wait(queries.map(
|
||||
(query) async => MapEntry(query.key, await query.refreshAll())))
|
||||
.then((qs) => Map.fromEntries(qs));
|
||||
}
|
||||
|
||||
static QueryClient of(BuildContext context) {
|
||||
return context
|
||||
.dependOnInheritedWidgetOfExactType<QueryClientProvider>()!
|
||||
|
||||
@@ -64,17 +64,20 @@ class InfiniteQueryPage<DataType, ErrorType, PageType> {
|
||||
class InfiniteQueryState<DataType, ErrorType, PageType> {
|
||||
final Set<InfiniteQueryPage<DataType, ErrorType, PageType>> pages;
|
||||
final InfiniteQueryFn<DataType, PageType> queryFn;
|
||||
final InfiniteQueryNextPage<DataType, PageType> nextPage;
|
||||
final InfiniteQueryNextPage<DataType, PageType> _nextPage;
|
||||
|
||||
const InfiniteQueryState({
|
||||
required this.pages,
|
||||
required this.queryFn,
|
||||
required this.nextPage,
|
||||
});
|
||||
required InfiniteQueryNextPage<DataType, PageType> nextPage,
|
||||
}) : _nextPage = nextPage;
|
||||
|
||||
PageType get lastPage => pages.last.page;
|
||||
PageType? get getNextPage =>
|
||||
nextPage(lastPage, pages.map((e) => e.data!).toList());
|
||||
PageType? get getNextPage => _nextPage(
|
||||
lastPage,
|
||||
pages.map((e) => e.data).whereType<DataType>().toList(),
|
||||
);
|
||||
|
||||
bool get hasNextPage => getNextPage != null;
|
||||
|
||||
InfiniteQueryState<DataType, ErrorType, PageType> copyWith({
|
||||
@@ -85,11 +88,23 @@ class InfiniteQueryState<DataType, ErrorType, PageType> {
|
||||
return InfiniteQueryState<DataType, ErrorType, PageType>(
|
||||
pages: pages ?? this.pages,
|
||||
queryFn: queryFn ?? this.queryFn,
|
||||
nextPage: nextPage ?? this.nextPage,
|
||||
nextPage: nextPage ?? this._nextPage,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PageEvent<T, P> {
|
||||
final P page;
|
||||
final T data;
|
||||
const PageEvent(this.page, this.data);
|
||||
|
||||
factory PageEvent.fromPage(
|
||||
InfiniteQueryPage page,
|
||||
) {
|
||||
return PageEvent(page.page as P, page.data as T);
|
||||
}
|
||||
}
|
||||
|
||||
class InfiniteQuery<DataType, ErrorType, KeyType, PageType>
|
||||
extends StateNotifier<InfiniteQueryState<DataType, ErrorType, PageType>>
|
||||
with Retryer<DataType, ErrorType> {
|
||||
@@ -106,7 +121,9 @@ class InfiniteQuery<DataType, ErrorType, KeyType, PageType>
|
||||
this.retryConfig = DefaultConstants.retryConfig,
|
||||
this.refreshConfig = DefaultConstants.refreshConfig,
|
||||
this.jsonConfig,
|
||||
}) : super(InfiniteQueryState<DataType, ErrorType, PageType>(
|
||||
}) : _dataController = StreamController.broadcast(),
|
||||
_errorController = StreamController.broadcast(),
|
||||
super(InfiniteQueryState<DataType, ErrorType, PageType>(
|
||||
pages: {
|
||||
InfiniteQueryPage<DataType, ErrorType, PageType>(
|
||||
page: initialParam,
|
||||
@@ -119,17 +136,30 @@ class InfiniteQuery<DataType, ErrorType, KeyType, PageType>
|
||||
)) {
|
||||
if (jsonConfig != null) {
|
||||
_mutex.protect(() async {
|
||||
final json = await _box.get(key.toString());
|
||||
final Map? json = await _box.get(key.value);
|
||||
if (json != null) {
|
||||
state = state.copyWith(
|
||||
pages: json.map(
|
||||
(key, value) => MapEntry(
|
||||
key as PageType,
|
||||
jsonConfig!.fromJson(value),
|
||||
),
|
||||
),
|
||||
pages: json.entries
|
||||
.map(
|
||||
(entry) => InfiniteQueryPage<DataType, ErrorType, PageType>(
|
||||
page: entry.key as PageType,
|
||||
data: jsonConfig!.fromJson(
|
||||
Map.castFrom<dynamic, dynamic, String, dynamic>(
|
||||
entry.value,
|
||||
)),
|
||||
// this makes the page loaded from cache `stale`
|
||||
updatedAt:
|
||||
DateTime.now().subtract(refreshConfig.staleDuration),
|
||||
staleDuration: refreshConfig.staleDuration,
|
||||
),
|
||||
)
|
||||
.toSet(),
|
||||
);
|
||||
}
|
||||
}).then((_) {
|
||||
if (hasListeners) {
|
||||
return fetch();
|
||||
}
|
||||
});
|
||||
}
|
||||
if (refreshConfig.refreshInterval > Duration.zero)
|
||||
@@ -146,12 +176,18 @@ class InfiniteQuery<DataType, ErrorType, KeyType, PageType>
|
||||
|
||||
final _mutex = Mutex();
|
||||
final _box = Hive.lazyBox("cache");
|
||||
final StreamController<PageEvent<DataType, PageType>> _dataController;
|
||||
final StreamController<PageEvent<ErrorType, PageType>> _errorController;
|
||||
|
||||
List<DataType> get pages =>
|
||||
state.pages.map((e) => e.data).whereType<DataType>().toList();
|
||||
List<ErrorType> get errors =>
|
||||
state.pages.map((e) => e.error).whereType<ErrorType>().toList();
|
||||
PageType get lastPage => state.lastPage;
|
||||
Stream<PageEvent<DataType, PageType>> get dataStream =>
|
||||
_dataController.stream;
|
||||
Stream<PageEvent<ErrorType, PageType>> get errorStream =>
|
||||
_errorController.stream;
|
||||
|
||||
bool get isLoadingPage => !hasPageData && !hasPageError && _mutex.isLocked;
|
||||
bool get isRefreshingPage => (hasPageData || hasPageError) && _mutex.isLocked;
|
||||
@@ -160,8 +196,8 @@ class InfiniteQuery<DataType, ErrorType, KeyType, PageType>
|
||||
bool get hasPages => pages.isNotEmpty;
|
||||
bool get hasErrors => errors.isNotEmpty;
|
||||
|
||||
bool get hasPageData => state.pages.last.data != null;
|
||||
bool get hasPageError => state.pages.last.error != null;
|
||||
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;
|
||||
|
||||
@@ -171,51 +207,54 @@ class InfiniteQuery<DataType, ErrorType, KeyType, PageType>
|
||||
() => state.queryFn(page),
|
||||
config: retryConfig,
|
||||
onSuccessful: (data) async {
|
||||
final dataPage = state.pages
|
||||
.firstWhere(
|
||||
(e) => e.page == page,
|
||||
orElse: () => InfiniteQueryPage<DataType, ErrorType, PageType>(
|
||||
page: page,
|
||||
updatedAt: DateTime.now(),
|
||||
staleDuration: refreshConfig.staleDuration,
|
||||
),
|
||||
)
|
||||
.copyWith(data: data);
|
||||
state = state.copyWith(
|
||||
pages: {
|
||||
...state.pages,
|
||||
state.pages
|
||||
.firstWhere(
|
||||
(e) => e.page == page,
|
||||
orElse: () =>
|
||||
InfiniteQueryPage<DataType, ErrorType, PageType>(
|
||||
page: page,
|
||||
updatedAt: DateTime.now(),
|
||||
staleDuration: refreshConfig.staleDuration,
|
||||
),
|
||||
)
|
||||
.copyWith(data: data),
|
||||
},
|
||||
pages: {...state.pages..remove(dataPage), dataPage},
|
||||
);
|
||||
if (dataPage.data != null)
|
||||
_dataController.add(PageEvent.fromPage(dataPage));
|
||||
if (jsonConfig != null) {
|
||||
await _box.put(
|
||||
key.toString(),
|
||||
state.pages.map(
|
||||
(e) => MapEntry(
|
||||
e.page,
|
||||
e.data != null ? jsonConfig!.toJson(e.data!) : null,
|
||||
key.value,
|
||||
Map.fromEntries(
|
||||
state.pages.map(
|
||||
(e) => MapEntry(
|
||||
e.page,
|
||||
e.data != null ? jsonConfig!.toJson(e.data!) : null,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
onFailed: (error) {
|
||||
final errorPage = state.pages
|
||||
.firstWhere(
|
||||
(e) => e.page == page,
|
||||
orElse: () => InfiniteQueryPage<DataType, ErrorType, PageType>(
|
||||
page: page,
|
||||
updatedAt: DateTime.now(),
|
||||
staleDuration: refreshConfig.staleDuration,
|
||||
),
|
||||
)
|
||||
.copyWith(error: error);
|
||||
state = state.copyWith(
|
||||
pages: {
|
||||
...state.pages,
|
||||
state.pages
|
||||
.firstWhere(
|
||||
(e) => e.page == page,
|
||||
orElse: () =>
|
||||
InfiniteQueryPage<DataType, ErrorType, PageType>(
|
||||
page: page,
|
||||
updatedAt: DateTime.now(),
|
||||
staleDuration: refreshConfig.staleDuration,
|
||||
),
|
||||
)
|
||||
.copyWith(error: error),
|
||||
...state.pages..remove(errorPage),
|
||||
errorPage,
|
||||
},
|
||||
);
|
||||
if (errorPage.error != null)
|
||||
_errorController.add(PageEvent.fromPage(errorPage));
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -246,14 +285,54 @@ class InfiniteQuery<DataType, ErrorType, KeyType, PageType>
|
||||
|
||||
Future<DataType?> fetchNext() async {
|
||||
final nextPage = state.getNextPage;
|
||||
if (_mutex.isLocked || nextPage != null) {
|
||||
return state.pages.firstWhereOrNull((e) => e.page == nextPage)?.data;
|
||||
if (_mutex.isLocked || nextPage == null) {
|
||||
return state.pages.lastOrNull?.data;
|
||||
}
|
||||
return await _operation(nextPage!).then((_) {
|
||||
return await _operation(nextPage).then((_) {
|
||||
return state.pages.firstWhereOrNull((e) => e.page == nextPage)?.data;
|
||||
});
|
||||
}
|
||||
|
||||
void updateQueryFn(InfiniteQueryFn<DataType, PageType> queryFn) {
|
||||
if (state.queryFn == queryFn) return;
|
||||
state = state.copyWith(queryFn: queryFn);
|
||||
if (refreshConfig.refreshOnQueryFnChange) {
|
||||
refreshAll();
|
||||
} else {
|
||||
Future.wait(
|
||||
state.pages.map((page) async {
|
||||
if (page.isStale) {
|
||||
return await refresh(page.page);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void updateNextPageFn(InfiniteQueryNextPage<DataType, PageType> nextPage) {
|
||||
state = state.copyWith(nextPage: nextPage);
|
||||
}
|
||||
|
||||
void setPageData(PageType page, DataType data) {
|
||||
final newPage = state.pages
|
||||
.firstWhere(
|
||||
(e) => e.page == page,
|
||||
orElse: () => InfiniteQueryPage<DataType, ErrorType, PageType>(
|
||||
page: page,
|
||||
updatedAt: DateTime.now(),
|
||||
staleDuration: refreshConfig.staleDuration,
|
||||
),
|
||||
)
|
||||
.copyWith(data: data);
|
||||
|
||||
state = state.copyWith(
|
||||
pages: {
|
||||
...state.pages..remove(newPage),
|
||||
newPage,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
RemoveListener addListener(
|
||||
Listener<InfiniteQueryState<DataType, ErrorType, PageType>> listener, {
|
||||
@@ -276,4 +355,13 @@ class InfiniteQuery<DataType, ErrorType, KeyType, PageType>
|
||||
@override
|
||||
operator ==(Object other) =>
|
||||
identical(this, other) || other is InfiniteQuery && key == other.key;
|
||||
|
||||
@override
|
||||
int get hashCode => key.hashCode;
|
||||
|
||||
InfiniteQuery<NewDataType, NewErrorType, NewKeyType, NewPageType>
|
||||
cast<NewDataType, NewErrorType, NewKeyType, NewPageType>() {
|
||||
return this
|
||||
as InfiniteQuery<NewDataType, NewErrorType, NewKeyType, NewPageType>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,10 +35,11 @@ class QueryState<DataType, ErrorType> {
|
||||
QueryState<DataType, ErrorType> copyWith({
|
||||
DataType? data,
|
||||
ErrorType? error,
|
||||
DateTime? updatedAt,
|
||||
QueryFn<DataType>? queryFn,
|
||||
}) {
|
||||
return QueryState<DataType, ErrorType>(
|
||||
updatedAt: DateTime.now(),
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
staleDuration: staleDuration,
|
||||
data: data ?? this.data,
|
||||
error: error ?? this.error,
|
||||
@@ -80,7 +81,7 @@ class Query<DataType, ErrorType, KeyType>
|
||||
_initial = jsonConfig!.fromJson(
|
||||
Map.castFrom<dynamic, dynamic, String, dynamic>(json),
|
||||
);
|
||||
state = state.copyWith(data: _initial);
|
||||
state = state.copyWith(data: _initial, updatedAt: DateTime.now());
|
||||
}
|
||||
}).then((_) {
|
||||
if (hasListeners) {
|
||||
@@ -124,7 +125,10 @@ class Query<DataType, ErrorType, KeyType>
|
||||
state.queryFn,
|
||||
config: retryConfig,
|
||||
onSuccessful: (DataType? data) {
|
||||
state = state.copyWith(data: data);
|
||||
state = state.copyWith(
|
||||
data: data,
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
if (data != null) _dataController.add(data);
|
||||
if (jsonConfig != null && data != null) {
|
||||
_box.put(
|
||||
@@ -134,7 +138,7 @@ class Query<DataType, ErrorType, KeyType>
|
||||
}
|
||||
},
|
||||
onFailed: (ErrorType? error) {
|
||||
state = state.copyWith(error: error);
|
||||
state = state.copyWith(error: error, updatedAt: DateTime.now());
|
||||
if (error != null) _errorController.add(error);
|
||||
},
|
||||
);
|
||||
@@ -154,17 +158,14 @@ class Query<DataType, ErrorType, KeyType>
|
||||
|
||||
void updateQueryFn(QueryFn<DataType> queryFn) {
|
||||
if (state.queryFn == queryFn) return;
|
||||
// updatedAt is updated with copyWith so storing it
|
||||
// here to check if the query is stale later
|
||||
final stale = state.isStale;
|
||||
state = state.copyWith(queryFn: queryFn);
|
||||
if (stale || refreshConfig.refreshOnQueryFnChange) {
|
||||
if (state.isStale || refreshConfig.refreshOnQueryFnChange) {
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void setData(DataType data) {
|
||||
state = state.copyWith(data: data);
|
||||
state = state.copyWith(data: data, updatedAt: DateTime.now());
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fl_query/src/collections/retry_config.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
mixin Retryer<T, E> {
|
||||
void retryOperation(
|
||||
@@ -19,9 +20,20 @@ mixin Retryer<T, E> {
|
||||
final result = await completer.future;
|
||||
onSuccessful(result);
|
||||
break;
|
||||
} catch (e) {
|
||||
if (attempts == config.maxRetries - 1 && e is E?) {
|
||||
onFailed(e as E?);
|
||||
} catch (e, stack) {
|
||||
if (attempts == config.maxRetries - 1) {
|
||||
if (e is E?) {
|
||||
onFailed(e as E?);
|
||||
} else {
|
||||
FlutterError.reportError(
|
||||
FlutterErrorDetails(
|
||||
exception: e,
|
||||
library: 'fl_query',
|
||||
context: ErrorDescription('retryOperation'),
|
||||
stack: stack,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fl_query/src/collections/default_configs.dart';
|
||||
import 'package:fl_query/src/collections/json_config.dart';
|
||||
import 'package:fl_query/src/collections/refresh_config.dart';
|
||||
import 'package:fl_query/src/collections/retry_config.dart';
|
||||
import 'package:fl_query/src/core/client.dart';
|
||||
import 'package:fl_query/src/core/infinite_query.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
typedef InfiniteQueryBuilderFn<DataType, ErrorType, KeyType, PageType> = Widget
|
||||
Function(
|
||||
BuildContext context,
|
||||
InfiniteQuery<DataType, ErrorType, KeyType, PageType> query,
|
||||
);
|
||||
|
||||
class InfiniteQueryBuilder<DataType, ErrorType, KeyType, PageType>
|
||||
extends StatefulWidget {
|
||||
final InfiniteQueryFn<DataType, PageType> queryFn;
|
||||
final ValueKey<KeyType> queryKey;
|
||||
|
||||
final PageType initialPage;
|
||||
final InfiniteQueryNextPage<DataType, PageType> nextPage;
|
||||
|
||||
final RetryConfig retryConfig;
|
||||
final RefreshConfig refreshConfig;
|
||||
final JsonConfig<DataType>? jsonConfig;
|
||||
|
||||
final ValueChanged<PageEvent<DataType, PageType>>? onData;
|
||||
final ValueChanged<PageEvent<ErrorType, PageType>>? onError;
|
||||
|
||||
// widget specific
|
||||
final bool enabled;
|
||||
final InfiniteQueryBuilderFn<DataType, ErrorType, KeyType, PageType> builder;
|
||||
|
||||
const InfiniteQueryBuilder(
|
||||
this.queryKey,
|
||||
this.queryFn, {
|
||||
required this.nextPage,
|
||||
required this.builder,
|
||||
required this.initialPage,
|
||||
this.retryConfig = DefaultConstants.retryConfig,
|
||||
this.refreshConfig = DefaultConstants.refreshConfig,
|
||||
this.jsonConfig,
|
||||
this.onData,
|
||||
this.onError,
|
||||
this.enabled = true,
|
||||
super.key,
|
||||
}) : assert(
|
||||
(jsonConfig != null && enabled) || jsonConfig == null,
|
||||
'jsonConfig is only supported when enabled is true',
|
||||
);
|
||||
|
||||
@override
|
||||
State<InfiniteQueryBuilder<DataType, ErrorType, KeyType, PageType>>
|
||||
createState() =>
|
||||
_InfiniteQueryBuilderState<DataType, ErrorType, KeyType, PageType>();
|
||||
}
|
||||
|
||||
class _InfiniteQueryBuilderState<DataType, ErrorType, KeyType, PageType>
|
||||
extends State<
|
||||
InfiniteQueryBuilder<DataType, ErrorType, KeyType, PageType>> {
|
||||
InfiniteQuery<DataType, ErrorType, KeyType, PageType>? query;
|
||||
|
||||
VoidCallback? removeListener;
|
||||
|
||||
StreamSubscription<PageEvent<DataType, PageType>>? dataSubscription;
|
||||
StreamSubscription<PageEvent<ErrorType, PageType>>? errorSubscription;
|
||||
|
||||
void update(_) {
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
Future<void> initialize() async {
|
||||
setState(() {
|
||||
query = QueryClient.of(context).createInfiniteQuery(
|
||||
widget.queryKey,
|
||||
widget.queryFn,
|
||||
initialParam: widget.initialPage,
|
||||
nextPage: widget.nextPage,
|
||||
retryConfig: widget.retryConfig,
|
||||
refreshConfig: widget.refreshConfig,
|
||||
jsonConfig: widget.jsonConfig,
|
||||
);
|
||||
|
||||
dataSubscription = query!.dataStream.listen(widget.onData);
|
||||
errorSubscription = query!.errorStream.listen(widget.onError);
|
||||
|
||||
removeListener = query!.addListener(update);
|
||||
});
|
||||
if (widget.enabled) {
|
||||
await query!.fetch();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
initialize();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
dataSubscription?.cancel();
|
||||
errorSubscription?.cancel();
|
||||
removeListener?.call();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
if (oldWidget.queryKey != widget.queryKey) {
|
||||
dataSubscription?.cancel();
|
||||
errorSubscription?.cancel();
|
||||
removeListener?.call();
|
||||
initialize();
|
||||
return;
|
||||
} else if (oldWidget.enabled != widget.enabled && widget.enabled) {
|
||||
query!.fetch();
|
||||
}
|
||||
if (oldWidget.queryFn != widget.queryFn) {
|
||||
query!.updateQueryFn(widget.queryFn);
|
||||
}
|
||||
if (oldWidget.nextPage != widget.nextPage) {
|
||||
query!.updateNextPageFn(widget.nextPage);
|
||||
}
|
||||
if (oldWidget.onData != widget.onData) {
|
||||
dataSubscription?.cancel();
|
||||
dataSubscription = query!.dataStream.listen(widget.onData);
|
||||
}
|
||||
if (oldWidget.onError != widget.onError) {
|
||||
errorSubscription?.cancel();
|
||||
errorSubscription = query!.errorStream.listen(widget.onError);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (query == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return widget.builder(context, query!);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ class QueryBuilder<DataType, ErrorType, KeyType> extends StatefulWidget {
|
||||
final QueryFn<DataType> queryFn;
|
||||
final ValueKey<KeyType> queryKey;
|
||||
|
||||
final QueryBuilderFn builder;
|
||||
final DataType? initial;
|
||||
|
||||
final RetryConfig retryConfig;
|
||||
@@ -30,6 +29,7 @@ class QueryBuilder<DataType, ErrorType, KeyType> extends StatefulWidget {
|
||||
|
||||
// widget specific
|
||||
final bool enabled;
|
||||
final QueryBuilderFn<DataType, ErrorType, KeyType> builder;
|
||||
|
||||
const QueryBuilder(
|
||||
this.queryKey,
|
||||
@@ -44,7 +44,7 @@ class QueryBuilder<DataType, ErrorType, KeyType> extends StatefulWidget {
|
||||
this.enabled = true,
|
||||
super.key,
|
||||
}) : assert(
|
||||
enabled && jsonConfig != null,
|
||||
(jsonConfig != null && enabled) || jsonConfig == null,
|
||||
'jsonConfig is only supported when enabled is true',
|
||||
);
|
||||
|
||||
@@ -63,7 +63,7 @@ class _QueryBuilderState<DataType, ErrorType, KeyType>
|
||||
StreamSubscription<ErrorType>? errorSubscription;
|
||||
|
||||
void update(_) {
|
||||
setState(() {});
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
Future<void> initialize() async {
|
||||
@@ -115,6 +115,8 @@ class _QueryBuilderState<DataType, ErrorType, KeyType>
|
||||
removeListener?.call();
|
||||
initialize();
|
||||
return;
|
||||
} else if (oldWidget.enabled != widget.enabled && widget.enabled) {
|
||||
query!.fetch();
|
||||
}
|
||||
if (oldWidget.queryFn != widget.queryFn) {
|
||||
query!.updateQueryFn(widget.queryFn);
|
||||
@@ -147,7 +149,8 @@ class _QueryBuilderState<DataType, ErrorType, KeyType>
|
||||
DiagnosticsProperty<ValueKey<KeyType>>('queryKey', widget.queryKey),
|
||||
);
|
||||
properties.add(
|
||||
DiagnosticsProperty<QueryBuilderFn>('builder', widget.builder),
|
||||
DiagnosticsProperty<QueryBuilderFn<DataType, ErrorType, KeyType>>(
|
||||
'builder', widget.builder),
|
||||
);
|
||||
properties.add(DiagnosticsProperty<DataType>('initial', widget.initial));
|
||||
properties.add(
|
||||
|
||||
@@ -26,7 +26,7 @@ class _StateNotifierListenableState<T>
|
||||
|
||||
void initialize() {
|
||||
removeListener = widget.notifier.addListener((_) {
|
||||
setState(() {});
|
||||
if (mounted) setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user