feat: cache, client, query builder add and refreshOnQueryFnChange option

fix: retryer wrong logic
This commit is contained in:
Kingkor Roy Tirtho
2023-02-14 21:50:06 +06:00
parent 45b6f92316
commit 18584a3c57
136 changed files with 5106 additions and 110 deletions
@@ -12,5 +12,8 @@ abstract class DefaultConstants {
staleDuration: Duration(seconds: 10),
refreshInterval: Duration(seconds: 5),
refreshOnMount: true,
refreshOnQueryFnChange: false,
);
static const Duration cacheDuration = Duration(minutes: 5);
}
@@ -2,10 +2,12 @@ class RefreshConfig {
final Duration staleDuration;
final Duration refreshInterval;
final bool refreshOnMount;
final bool refreshOnQueryFnChange;
const RefreshConfig({
required this.staleDuration,
required this.refreshInterval,
required this.refreshOnMount,
required this.refreshOnQueryFnChange,
});
}
+43 -1
View File
@@ -1 +1,43 @@
class QueryCache {}
import 'dart:async';
import 'package:collection/collection.dart';
import 'package:fl_query/src/collections/default_configs.dart';
import 'package:fl_query/src/core/infinite_query.dart';
import 'package:fl_query/src/core/query.dart';
class QueryCache {
final Set<Query> _queries;
final Set<InfiniteQuery> _infiniteQueries;
final Duration cacheDuration;
QueryCache({
this.cacheDuration = DefaultConstants.cacheDuration,
}) : _queries = Set<Query>(),
_infiniteQueries = Set<InfiniteQuery>() {
Timer.periodic(cacheDuration, (timer) {
_queries.removeWhere((query) => query.isInactive);
_infiniteQueries.removeWhere((query) => query.isInactive);
});
}
UnmodifiableSetView<Query> get queries => UnmodifiableSetView(_queries);
UnmodifiableSetView<InfiniteQuery> get infiniteQueries =>
UnmodifiableSetView(_infiniteQueries);
void addQuery(Query query) {
_queries.add(query);
}
void addInfiniteQuery(InfiniteQuery query) {
_infiniteQueries.add(query);
}
void removeQuery(Query query) {
_queries.remove(query);
}
void removeInfiniteQuery(InfiniteQuery query) {
_infiniteQueries.remove(query);
}
}
+101 -1
View File
@@ -1 +1,101 @@
class QueryClient {}
import 'package:collection/collection.dart';
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/cache.dart';
import 'package:fl_query/src/core/provider.dart';
import 'package:fl_query/src/core/query.dart';
import 'package:flutter/material.dart';
import 'package:hive_flutter/adapters.dart';
class QueryClient {
final QueryCache cache;
QueryClient({QueryCache? cache}) : this.cache = cache ?? QueryCache();
Query<DataType, ErrorType, KeyType> createQuery<DataType, ErrorType, KeyType>(
ValueKey<KeyType> key,
QueryFn<DataType> queryFn, {
DataType? initial,
RetryConfig retryConfig = DefaultConstants.retryConfig,
RefreshConfig refreshConfig = DefaultConstants.refreshConfig,
JsonConfig<DataType>? jsonConfig,
}) {
final query = cache.queries
.firstWhere(
(query) => query.key == key,
orElse: () => Query<DataType, ErrorType, KeyType>(
key,
queryFn,
initial: initial,
retryConfig: retryConfig,
refreshConfig: refreshConfig,
jsonConfig: jsonConfig,
),
)
.cast<DataType, ErrorType, KeyType>();
cache.addQuery(query);
return query;
}
Future<DataType?> fetchQuery<DataType, ErrorType, KeyType>(
ValueKey<KeyType> key,
QueryFn<DataType> queryFn, {
DataType? initial,
RetryConfig retryConfig = DefaultConstants.retryConfig,
RefreshConfig refreshConfig = DefaultConstants.refreshConfig,
JsonConfig<DataType>? jsonConfig,
}) async {
final query = createQuery<DataType, ErrorType, KeyType>(
key,
queryFn,
initial: initial,
retryConfig: retryConfig,
refreshConfig: refreshConfig,
jsonConfig: jsonConfig,
);
return await query.fetch();
}
Query<DataType, ErrorType, KeyType>? getQuery<DataType, ErrorType, KeyType>(
ValueKey<KeyType> key) {
return cache.queries
.firstWhereOrNull((query) => query.key == key)
?.cast<DataType, ErrorType, KeyType>();
}
List<Query> getQueries(List<ValueKey> keys) {
return cache.queries.where((query) => keys.contains(query.key)).toList();
}
Future<DataType?> refreshQuery<DataType, ErrorType, KeyType>(
ValueKey<KeyType> key,
{DataType? initial}) async {
final query = getQuery<DataType, ErrorType, KeyType>(key);
if (query == null) return null;
return await query.refresh();
}
Future<List> refreshQueries(List<ValueKey> keys) async {
final queries = getQueries(keys);
return await Future.wait(queries.map((query) => query.refresh()));
}
static QueryClient of(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<QueryClientProvider>()!
.client;
}
static QueryClient? maybeOf(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<QueryClientProvider>()
?.client;
}
static Future<void> initialize({String? cacheDir}) async {
await Hive.initFlutter(cacheDir);
await Hive.openLazyBox('cache');
}
}
@@ -6,21 +6,22 @@ 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/retryer.dart';
import 'package:flutter/material.dart';
import 'package:flutter/material.dart' hide Listener;
import 'package:hive_flutter/adapters.dart';
import 'package:mutex/mutex.dart';
import 'package:state_notifier/state_notifier.dart';
typedef InfiniteQueryFn<T, P> = FutureOr<T?> Function(P page);
typedef InfiniteQueryNextPage<T, P> = P? Function(
P lastPage,
List<T> pages,
typedef InfiniteQueryFn<DataType, PageType> = FutureOr<DataType?> Function(
PageType page);
typedef InfiniteQueryNextPage<DataType, PageType> = PageType? Function(
PageType lastPage,
List<DataType> pages,
);
class InfiniteQueryPage<T, E, P> {
final P page;
final T? data;
final E? error;
class InfiniteQueryPage<DataType, ErrorType, PageType> {
final PageType page;
final DataType? data;
final ErrorType? error;
final DateTime updatedAt;
final Duration staleDuration;
@@ -35,11 +36,11 @@ class InfiniteQueryPage<T, E, P> {
bool get isStale => DateTime.now().difference(updatedAt) > staleDuration;
InfiniteQueryPage<T, E, P> copyWith({
T? data,
E? error,
InfiniteQueryPage<DataType, ErrorType, PageType> copyWith({
DataType? data,
ErrorType? error,
}) {
return InfiniteQueryPage<T, E, P>(
return InfiniteQueryPage<DataType, ErrorType, PageType>(
page: page,
updatedAt: DateTime.now(),
staleDuration: staleDuration,
@@ -52,17 +53,18 @@ class InfiniteQueryPage<T, E, P> {
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is InfiniteQueryPage<T, E, P> && other.page == page;
return other is InfiniteQueryPage<DataType, ErrorType, PageType> &&
other.page == page;
}
@override
int get hashCode => page.hashCode;
}
class InfiniteQueryState<T, E, P> {
final Set<InfiniteQueryPage<T, E, P>> pages;
final InfiniteQueryFn<T, P> queryFn;
final InfiniteQueryNextPage<T, P> nextPage;
class InfiniteQueryState<DataType, ErrorType, PageType> {
final Set<InfiniteQueryPage<DataType, ErrorType, PageType>> pages;
final InfiniteQueryFn<DataType, PageType> queryFn;
final InfiniteQueryNextPage<DataType, PageType> nextPage;
const InfiniteQueryState({
required this.pages,
@@ -70,16 +72,17 @@ class InfiniteQueryState<T, E, P> {
required this.nextPage,
});
P get lastPage => pages.last.page;
P? get getNextPage => nextPage(lastPage, pages.map((e) => e.data!).toList());
PageType get lastPage => pages.last.page;
PageType? get getNextPage =>
nextPage(lastPage, pages.map((e) => e.data!).toList());
bool get hasNextPage => getNextPage != null;
InfiniteQueryState<T, E, P> copyWith({
Set<InfiniteQueryPage<T, E, P>>? pages,
InfiniteQueryFn<T, P>? queryFn,
InfiniteQueryNextPage<T, P>? nextPage,
InfiniteQueryState<DataType, ErrorType, PageType> copyWith({
Set<InfiniteQueryPage<DataType, ErrorType, PageType>>? pages,
InfiniteQueryFn<DataType, PageType>? queryFn,
InfiniteQueryNextPage<DataType, PageType>? nextPage,
}) {
return InfiniteQueryState<T, E, P>(
return InfiniteQueryState<DataType, ErrorType, PageType>(
pages: pages ?? this.pages,
queryFn: queryFn ?? this.queryFn,
nextPage: nextPage ?? this.nextPage,
@@ -87,24 +90,25 @@ class InfiniteQueryState<T, E, P> {
}
}
class InfiniteQuery<T, E, K, P>
extends StateNotifier<InfiniteQueryState<T, E, P>> with Retryer<T, E> {
final ValueKey<K> key;
class InfiniteQuery<DataType, ErrorType, KeyType, PageType>
extends StateNotifier<InfiniteQueryState<DataType, ErrorType, PageType>>
with Retryer<DataType, ErrorType> {
final ValueKey<KeyType> key;
final RetryConfig retryConfig;
final RefreshConfig refreshConfig;
final JsonConfig<T>? jsonConfig;
final JsonConfig<DataType>? jsonConfig;
InfiniteQuery(
this.key,
InfiniteQueryFn<T, P> queryFn, {
required InfiniteQueryNextPage<T, P> nextPage,
required P initialParam,
InfiniteQueryFn<DataType, PageType> queryFn, {
required InfiniteQueryNextPage<DataType, PageType> nextPage,
required PageType initialParam,
this.retryConfig = DefaultConstants.retryConfig,
this.refreshConfig = DefaultConstants.refreshConfig,
this.jsonConfig,
}) : super(InfiniteQueryState<T, E, P>(
}) : super(InfiniteQueryState<DataType, ErrorType, PageType>(
pages: {
InfiniteQueryPage<T, E, P>(
InfiniteQueryPage<DataType, ErrorType, PageType>(
page: initialParam,
updatedAt: DateTime.now(),
staleDuration: refreshConfig.staleDuration,
@@ -115,12 +119,12 @@ class InfiniteQuery<T, E, K, P>
)) {
if (jsonConfig != null) {
_mutex.protect(() async {
final json = await _box.get(key.value);
final json = await _box.get(key.toString());
if (json != null) {
state = state.copyWith(
pages: json.map(
(key, value) => MapEntry(
key as P,
key as PageType,
jsonConfig!.fromJson(value),
),
),
@@ -143,12 +147,15 @@ class InfiniteQuery<T, E, K, P>
final _mutex = Mutex();
final _box = Hive.lazyBox("cache");
List<T> get pages => state.pages.map((e) => e.data).whereType<T>().toList();
List<E> get errors => state.pages.map((e) => e.error).whereType<E>().toList();
P get lastPage => state.lastPage;
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;
bool get isLoadingPage => !hasPageData && !hasPageError && _mutex.isLocked;
bool get isRefreshingPage => (hasPageData || hasPageError) && _mutex.isLocked;
bool get isInactive => !hasListeners;
bool get hasPages => pages.isNotEmpty;
bool get hasErrors => errors.isNotEmpty;
@@ -158,7 +165,7 @@ class InfiniteQuery<T, E, K, P>
bool get hasNextPage => state.hasNextPage;
Future<void> _operation(P page) {
Future<void> _operation(PageType page) {
return _mutex.protect(() async {
retryOperation(
() => state.queryFn(page),
@@ -170,7 +177,8 @@ class InfiniteQuery<T, E, K, P>
state.pages
.firstWhere(
(e) => e.page == page,
orElse: () => InfiniteQueryPage<T, E, P>(
orElse: () =>
InfiniteQueryPage<DataType, ErrorType, PageType>(
page: page,
updatedAt: DateTime.now(),
staleDuration: refreshConfig.staleDuration,
@@ -181,7 +189,7 @@ class InfiniteQuery<T, E, K, P>
);
if (jsonConfig != null) {
await _box.put(
key.value,
key.toString(),
state.pages.map(
(e) => MapEntry(
e.page,
@@ -198,7 +206,8 @@ class InfiniteQuery<T, E, K, P>
state.pages
.firstWhere(
(e) => e.page == page,
orElse: () => InfiniteQueryPage<T, E, P>(
orElse: () =>
InfiniteQueryPage<DataType, ErrorType, PageType>(
page: page,
updatedAt: DateTime.now(),
staleDuration: refreshConfig.staleDuration,
@@ -212,14 +221,14 @@ class InfiniteQuery<T, E, K, P>
});
}
Future<T?> fetch() async {
Future<DataType?> fetch() async {
final lastPage = state.lastPage;
if (_mutex.isLocked || hasPageData || hasPageError)
return state.pages.last.data;
return await _operation(lastPage).then((_) => state.pages.last.data);
}
Future<T?> refresh([P? page]) async {
Future<DataType?> refresh([PageType? page]) async {
page ??= lastPage;
if (_mutex.isLocked)
return state.pages.firstWhereOrNull((e) => e.page == page)?.data;
@@ -228,14 +237,14 @@ class InfiniteQuery<T, E, K, P>
});
}
Future<List<T>?> refreshAll() async {
Future<List<DataType>?> refreshAll() async {
if (_mutex.isLocked) return pages;
return await Future.wait(
state.pages.map((e) => _operation(e.page)),
).then((_) => pages);
}
Future<T?> fetchNext() async {
Future<DataType?> fetchNext() async {
final nextPage = state.getNextPage;
if (_mutex.isLocked || nextPage != null) {
return state.pages.firstWhereOrNull((e) => e.page == nextPage)?.data;
@@ -244,4 +253,27 @@ class InfiniteQuery<T, E, K, P>
return state.pages.firstWhereOrNull((e) => e.page == nextPage)?.data;
});
}
@override
RemoveListener addListener(
Listener<InfiniteQueryState<DataType, ErrorType, PageType>> listener, {
bool fireImmediately = true,
}) {
if (refreshConfig.refreshOnMount) {
refreshAll();
} else {
Future.wait(
state.pages.map((page) async {
if (page.isStale) {
return await refresh(page.page);
}
}),
);
}
return super.addListener(listener, fireImmediately: fireImmediately);
}
@override
operator ==(Object other) =>
identical(this, other) || other is InfiniteQuery && key == other.key;
}
+71 -31
View File
@@ -5,17 +5,17 @@ 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/retryer.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart' hide Listener;
import 'package:hive_flutter/adapters.dart';
import 'package:mutex/mutex.dart';
import 'package:state_notifier/state_notifier.dart';
typedef QueryFn<T> = FutureOr<T?> Function();
typedef QueryFn<DataType> = FutureOr<DataType?> Function();
class QueryState<T, E> {
final T? data;
final E? error;
final QueryFn<T> queryFn;
class QueryState<DataType, ErrorType> {
final DataType? data;
final ErrorType? error;
final QueryFn<DataType> queryFn;
final DateTime updatedAt;
final Duration staleDuration;
@@ -32,12 +32,12 @@ class QueryState<T, E> {
return DateTime.now().difference(updatedAt) > staleDuration;
}
QueryState<T, E> copyWith({
T? data,
E? error,
QueryFn<T>? queryFn,
QueryState<DataType, ErrorType> copyWith({
DataType? data,
ErrorType? error,
QueryFn<DataType>? queryFn,
}) {
return QueryState<T, E>(
return QueryState<DataType, ErrorType>(
updatedAt: DateTime.now(),
staleDuration: staleDuration,
data: data ?? this.data,
@@ -47,23 +47,26 @@ class QueryState<T, E> {
}
}
class Query<T, E, K> extends StateNotifier<QueryState<T, E>>
with Retryer<T, E> {
final ValueKey<K> key;
final T? initial;
class Query<DataType, ErrorType, KeyType>
extends StateNotifier<QueryState<DataType, ErrorType>>
with Retryer<DataType, ErrorType> {
final ValueKey<KeyType> key;
final DataType? initial;
final RefreshConfig refreshConfig;
final RetryConfig retryConfig;
final JsonConfig<T>? jsonConfig;
final JsonConfig<DataType>? jsonConfig;
Query(
this.key,
QueryFn<T> queryFn, {
QueryFn<DataType> queryFn, {
this.initial,
this.retryConfig = DefaultConstants.retryConfig,
this.refreshConfig = DefaultConstants.refreshConfig,
this.jsonConfig,
}) : _box = Hive.lazyBox("cache"),
super(QueryState<T, E>(
_dataController = StreamController<DataType>.broadcast(),
_errorController = StreamController<ErrorType>.broadcast(),
super(QueryState<DataType, ErrorType>(
updatedAt: DateTime.now(),
staleDuration: refreshConfig.staleDuration,
data: initial,
@@ -71,9 +74,13 @@ class Query<T, E, K> extends StateNotifier<QueryState<T, E>>
)) {
if (jsonConfig != null) {
_mutex.protect(() async {
final json = await _box.get(key);
final json = await _box.get(key.toString());
if (json != null) {
state = state.copyWith(data: jsonConfig!.fromJson(json));
state = state.copyWith(
data: jsonConfig!.fromJson(
Map.castFrom<dynamic, dynamic, String, dynamic>(json),
),
);
}
});
@@ -87,53 +94,86 @@ class Query<T, E, K> extends StateNotifier<QueryState<T, E>>
final LazyBox _box;
final _mutex = Mutex();
final StreamController<DataType> _dataController;
final StreamController<ErrorType> _errorController;
bool get isInitial => state.data == initial;
bool get isLoading => isInitial ? _mutex.isLocked : !hasData && !hasError;
bool get isRefreshing =>
((!isInitial && hasData) || hasError) && _mutex.isLocked;
bool get isInactive => !hasListeners;
bool get hasData => state.data != null;
bool get hasError => state.error != null;
T? get data => state.data;
E? get error => state.error;
DataType? get data => state.data;
ErrorType? get error => state.error;
Stream<DataType> get dataStream => _dataController.stream;
Stream<ErrorType> get errorStream => _errorController.stream;
Future<void> _operate() {
return _mutex.protect(() async {
retryOperation(
state.queryFn,
config: retryConfig,
onSuccessful: (T? data) {
onSuccessful: (DataType? data) {
state = state.copyWith(data: data);
if (jsonConfig != null) {
if (data != null) _dataController.add(data);
if (jsonConfig != null && data != null) {
_box.put(
key,
jsonConfig!.toJson(data!),
key.toString(),
jsonConfig!.toJson(data),
);
}
},
onFailed: (E? error) {
onFailed: (ErrorType? error) {
state = state.copyWith(error: error);
if (error != null) _errorController.add(error);
},
);
});
}
Future<T?> fetch() async {
Future<DataType?> fetch() async {
if (_mutex.isLocked || hasData || hasError) return state.data;
return _operate().then((_) => state.data);
}
Future<T?> refresh() async {
Future<DataType?> refresh() async {
if (_mutex.isLocked) return state.data;
return _operate().then((_) => state.data);
}
void updateQueryFn(QueryFn<T> queryFn) {
void updateQueryFn(QueryFn<DataType> queryFn) {
state = state.copyWith(queryFn: queryFn);
if (state.isStale || refreshConfig.refreshOnQueryFnChange) {
refresh();
}
}
void setData(T data) {
void setData(DataType data) {
state = state.copyWith(data: data);
}
@override
RemoveListener addListener(Listener<QueryState<DataType, ErrorType>> listener,
{bool fireImmediately = true}) {
if (state.isStale || refreshConfig.refreshOnMount) {
refresh();
}
return super.addListener(listener, fireImmediately: fireImmediately);
}
@override
operator ==(Object other) {
return identical(this, other) ||
(other is Query && key.value == other.key.value);
}
@override
int get hashCode => key.hashCode;
Query<NewDataType, NewErrorType, NewKeyType>
cast<NewDataType, NewErrorType, NewKeyType>() {
return this as Query<NewDataType, NewErrorType, NewKeyType>;
}
}
+28 -28
View File
@@ -1,39 +1,39 @@
import 'dart:async';
import 'package:fl_query/src/collections/retry_config.dart';
import 'package:flutter/foundation.dart';
mixin Retryer<T, E> {
VoidCallback retryOperation(
void retryOperation(
FutureOr<T?> Function() operation, {
required RetryConfig config,
required Function(T?) onSuccessful,
required Function(E?) onFailed,
}) {
int retries = 0;
late Timer timer;
VoidCallback? cancel;
void retry() {
if (retries < config.maxRetries) {
retries++;
timer = Timer(config.retryDelay, () async {
await Future.value(operation()).then((T? data) {
onSuccessful(data);
}).catchError((error) {
if (error is E) onFailed(error);
retry();
});
});
} else {
cancel?.call();
required void Function(T?) onSuccessful,
required void Function(E?) onFailed,
}) async {
for (int attempts = 0; attempts < config.maxRetries; attempts++) {
final completer = Completer<T?>();
await Future.delayed(
attempts == 0 ? Duration.zero : config.retryDelay,
operation,
).then(completer.complete).catchError(completer.completeError);
await Future.delayed(config.timeout, () {
if (!completer.isCompleted) {
completer.completeError(
TimeoutException(
'Operation timed out after ${config.timeout.inSeconds} seconds',
),
StackTrace.current,
);
}
});
try {
final result = await completer.future;
onSuccessful(result);
break;
} catch (e) {
if (attempts == config.maxRetries - 1 && e is E?) {
onFailed(e as E?);
}
}
}
cancel = timer.cancel;
timer = Timer(config.timeout, retry);
return cancel;
}
}
@@ -0,0 +1,135 @@
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/query.dart';
import 'package:flutter/material.dart';
typedef QueryBuilderFn<DataType, ErrorType, KeyType> = Widget Function(
BuildContext context,
Query<DataType, ErrorType, KeyType> query,
);
class QueryBuilder<DataType, ErrorType, KeyType> extends StatefulWidget {
final QueryFn<DataType> queryFn;
final ValueKey<KeyType> queryKey;
final QueryBuilderFn builder;
final DataType? initial;
final RetryConfig retryConfig;
final RefreshConfig refreshConfig;
final JsonConfig<DataType>? jsonConfig;
final ValueChanged<DataType>? onData;
final ValueChanged<ErrorType>? onError;
// widget specific
final bool enabled;
const QueryBuilder(
this.queryKey,
this.queryFn, {
required this.builder,
this.initial,
this.retryConfig = DefaultConstants.retryConfig,
this.refreshConfig = DefaultConstants.refreshConfig,
this.jsonConfig,
this.onData,
this.onError,
this.enabled = true,
super.key,
});
@override
State<QueryBuilder<DataType, ErrorType, KeyType>> createState() =>
_QueryBuilderState<DataType, ErrorType, KeyType>();
}
class _QueryBuilderState<DataType, ErrorType, KeyType>
extends State<QueryBuilder<DataType, ErrorType, KeyType>> {
Query<DataType, ErrorType, KeyType>? query;
VoidCallback? removeListener;
StreamSubscription<DataType>? dataSubscription;
StreamSubscription<ErrorType>? errorSubscription;
void update(_) {
setState(() {});
}
Future<void> initialize() async {
setState(() {
query = QueryClient.of(context).createQuery(
widget.queryKey,
widget.queryFn,
initial: widget.initial,
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((_) async {
await initialize();
});
}
@override
void dispose() {
dataSubscription?.cancel();
errorSubscription?.cancel();
removeListener?.call();
super.dispose();
}
@override
void didUpdateWidget(
QueryBuilder<DataType, ErrorType, KeyType> oldWidget,
) {
super.didUpdateWidget(oldWidget);
if (oldWidget.queryKey != widget.queryKey) {
dataSubscription?.cancel();
errorSubscription?.cancel();
removeListener?.call();
initialize();
return;
}
if (oldWidget.queryFn != widget.queryFn) {
query!.updateQueryFn(widget.queryFn);
}
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!);
}
}