removed dependOnQuery from beacuse of infinite renders
example created for various usecases with various features hooks now use standard hook API instead of using Hook Class variable query & mutation not working bug fix mutation onMutate listeners not getting called bug fix
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import 'package:fl_query/src/query_bowl.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
abstract class BaseOperation<Data, StatusType> extends ChangeNotifier {
|
||||
@@ -28,11 +29,14 @@ abstract class BaseOperation<Data, StatusType> extends ChangeNotifier {
|
||||
/// storage/cache
|
||||
Set<ValueKey<String>> _mounts = {};
|
||||
|
||||
final QueryBowl queryBowl;
|
||||
|
||||
BaseOperation({
|
||||
required this.cacheTime,
|
||||
required this.retries,
|
||||
required this.retryDelay,
|
||||
required this.status,
|
||||
required this.queryBowl,
|
||||
this.data,
|
||||
}) : updatedAt = DateTime.now();
|
||||
|
||||
|
||||
@@ -21,96 +21,63 @@ Mutation<T, V> useMutation<T extends Object, V>({
|
||||
MutationListener<V>? onMutate,
|
||||
List<Object?>? keys,
|
||||
}) {
|
||||
return use(_UseMutation<T, V>(
|
||||
job: job,
|
||||
onData: onData,
|
||||
onError: onError,
|
||||
onMutate: onMutate,
|
||||
keys: keys,
|
||||
));
|
||||
}
|
||||
final context = useContext();
|
||||
final QueryBowl queryBowl = QueryBowl.of(context);
|
||||
final ValueKey<String> uKey = useMemoized(() => ValueKey(uuid.v4()), []);
|
||||
final mutation =
|
||||
useRef(Mutation<T, V>.fromOptions(job, queryBowl: queryBowl));
|
||||
|
||||
class _UseMutation<T extends Object, V> extends Hook<Mutation<T, V>> {
|
||||
final MutationJob<T, V> job;
|
||||
final init = useCallback(() {
|
||||
mutation.value = queryBowl.addMutation<T, V>(
|
||||
mutation.value,
|
||||
onData: onData,
|
||||
onError: onError,
|
||||
onMutate: onMutate,
|
||||
key: uKey,
|
||||
);
|
||||
}, [mutation.value, job, onData, onError, onMutate, uKey]);
|
||||
|
||||
/// Called when the query returns new data, on query
|
||||
/// refetch or query gets expired
|
||||
final MutationListener<T>? onData;
|
||||
final disposeMutation = useCallback(() {
|
||||
mutation.value.unmount(uKey);
|
||||
if (onData != null) mutation.value.onDataListeners.remove(onData);
|
||||
if (onError != null) mutation.value.onErrorListeners.remove(onError);
|
||||
if (onMutate != null) mutation.value.onMutateListeners.remove(onMutate);
|
||||
}, [mutation.value, onData, onError, onMutate]);
|
||||
|
||||
/// Called when the query returns error
|
||||
final MutationListener<dynamic>? onError;
|
||||
final oldJob = usePrevious(job);
|
||||
final oldOnData = usePrevious(onData);
|
||||
final oldOnError = usePrevious(onError);
|
||||
final oldOnMutate = usePrevious(onMutate);
|
||||
|
||||
/// called right before the mutation is about to run
|
||||
///
|
||||
/// perfect scenario for doing optimistic updates
|
||||
final MutationListener<V>? onMutate;
|
||||
const _UseMutation({
|
||||
required this.job,
|
||||
this.onData,
|
||||
this.onError,
|
||||
this.onMutate,
|
||||
super.keys,
|
||||
useEffect(() {
|
||||
init();
|
||||
return disposeMutation;
|
||||
}, []);
|
||||
|
||||
useEffect(() {
|
||||
if (oldJob != null && oldJob.mutationKey != job.mutationKey) {
|
||||
disposeMutation();
|
||||
mutation.value = Mutation<T, V>.fromOptions(
|
||||
job,
|
||||
queryBowl: queryBowl,
|
||||
);
|
||||
init();
|
||||
} else {
|
||||
if (oldOnData != onData && oldOnData != null) {
|
||||
mutation.value.onDataListeners.remove(oldOnData);
|
||||
if (onData != null) mutation.value.onDataListeners.add(onData);
|
||||
}
|
||||
if (oldOnError != onError && oldOnError != null) {
|
||||
mutation.value.onErrorListeners.remove(oldOnError);
|
||||
if (onError != null) mutation.value.onErrorListeners.add(onError);
|
||||
}
|
||||
if (oldOnMutate != onMutate && oldOnMutate != null) {
|
||||
mutation.value.onMutateListeners.remove(oldOnMutate);
|
||||
if (onMutate != null) mutation.value.onMutateListeners.add(onMutate);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
@override
|
||||
HookState<Mutation<T, V>, Hook<Mutation<T, V>>> createState() =>
|
||||
_UseMutationHookState();
|
||||
}
|
||||
|
||||
class _UseMutationHookState<T extends Object, V>
|
||||
extends HookState<Mutation<T, V>, _UseMutation<T, V>> {
|
||||
late QueryBowl queryBowl;
|
||||
late final ValueKey<String> uKey;
|
||||
late Mutation<T, V> mutation;
|
||||
|
||||
@override
|
||||
void initHook() {
|
||||
super.initHook();
|
||||
uKey = ValueKey<String>(uuid.v4());
|
||||
mutation = Mutation<T, V>.fromOptions(hook.job);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
queryBowl = QueryBowl.of(context);
|
||||
mutation = queryBowl.addMutation<T, V>(
|
||||
mutation,
|
||||
onData: hook.onData,
|
||||
onError: hook.onError,
|
||||
onMutate: hook.onMutate,
|
||||
key: uKey,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateHook(_UseMutation<T, V> oldHook) {
|
||||
if (oldHook.onData != hook.onData && oldHook.onData != null) {
|
||||
mutation.onDataListeners.remove(oldHook.onData);
|
||||
if (hook.onData != null) mutation.onDataListeners.add(hook.onData!);
|
||||
}
|
||||
if (oldHook.onError != hook.onError && oldHook.onError != null) {
|
||||
mutation.onErrorListeners.remove(oldHook.onError);
|
||||
if (hook.onError != null) mutation.onErrorListeners.add(hook.onError!);
|
||||
}
|
||||
if (oldHook.onMutate != hook.onMutate && oldHook.onMutate != null) {
|
||||
mutation.onMutateListeners.remove(oldHook.onMutate);
|
||||
if (hook.onMutate != null) mutation.onMutateListeners.add(hook.onMutate!);
|
||||
}
|
||||
super.didUpdateHook(oldHook);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
mutation.unmount(uKey);
|
||||
if (hook.onData != null) mutation.onDataListeners.remove(hook.onData);
|
||||
if (hook.onError != null) mutation.onErrorListeners.remove(hook.onError);
|
||||
if (hook.onMutate != null) mutation.onMutateListeners.remove(hook.onMutate);
|
||||
}
|
||||
|
||||
@override
|
||||
Mutation<T, V> build(BuildContext context) {
|
||||
queryBowl = QueryBowl.of(context);
|
||||
return queryBowl.getMutation<T, V>(mutation.mutationKey) ?? mutation;
|
||||
}
|
||||
|
||||
@override
|
||||
String get debugLabel => 'useQuery';
|
||||
return queryBowl.getMutation<T, V>(job.mutationKey) ?? mutation.value;
|
||||
}
|
||||
|
||||
@@ -18,45 +18,59 @@ Query<T, Outside> useQuery<T extends Object, Outside>({
|
||||
List<Object?>? keys,
|
||||
}) {
|
||||
final context = useContext();
|
||||
QueryBowl queryBowl = QueryBowl.of(context);
|
||||
final QueryBowl queryBowl = QueryBowl.of(context);
|
||||
final ValueKey<String> uKey = useMemoized(() => ValueKey(uuid.v4()), []);
|
||||
Query<T, Outside> query = useMemoized(
|
||||
() => Query.fromOptions(
|
||||
job,
|
||||
externalData: externalData,
|
||||
queryBowl: queryBowl,
|
||||
onData: onData,
|
||||
onError: onError,
|
||||
),
|
||||
[]);
|
||||
final query = useRef(
|
||||
Query.fromOptions(
|
||||
job,
|
||||
externalData: externalData,
|
||||
queryBowl: queryBowl,
|
||||
),
|
||||
);
|
||||
|
||||
final oldJob = usePrevious(job);
|
||||
final oldExternalData = usePrevious(externalData);
|
||||
final oldOnData = usePrevious(onData);
|
||||
final oldOnError = usePrevious(onError);
|
||||
|
||||
useEffect(() {
|
||||
queryBowl.addQuery<T, Outside>(
|
||||
query,
|
||||
final init = useCallback(() {
|
||||
query.value = queryBowl.addQuery<T, Outside>(
|
||||
query.value,
|
||||
key: uKey,
|
||||
onData: onData,
|
||||
onError: onError,
|
||||
);
|
||||
final hasExternalDataChanged = query.externalData != null &&
|
||||
query.prevUsedExternalData != null &&
|
||||
!isShallowEqual(query.externalData!, query.prevUsedExternalData!);
|
||||
(query.fetched && query.refetchOnMount == true) || hasExternalDataChanged
|
||||
? query.refetch()
|
||||
: query.fetch();
|
||||
final hasExternalDataChanged = query.value.externalData != null &&
|
||||
query.value.prevUsedExternalData != null &&
|
||||
!isShallowEqual(
|
||||
query.value.externalData!, query.value.prevUsedExternalData!);
|
||||
(query.value.fetched && query.value.refetchOnMount == true) ||
|
||||
hasExternalDataChanged
|
||||
? query.value.refetch()
|
||||
: query.value.fetch();
|
||||
}, [queryBowl, query.value, uKey, onData, onError, job]);
|
||||
|
||||
return () {
|
||||
query.unmount(uKey);
|
||||
if (onData != null) query.onDataListeners.remove(onData);
|
||||
if (onError != null) query.onErrorListeners.remove(onError);
|
||||
};
|
||||
final disposeQuery = useCallback(() {
|
||||
query.value.unmount(uKey);
|
||||
if (onData != null) query.value.onDataListeners.remove(onData);
|
||||
if (onError != null) query.value.onErrorListeners.remove(onError);
|
||||
}, [query.value, onData, onError, uKey]);
|
||||
|
||||
useEffect(() {
|
||||
init();
|
||||
return disposeQuery;
|
||||
}, []);
|
||||
|
||||
useEffect(() {
|
||||
if (oldExternalData != null &&
|
||||
if (oldJob != null && oldJob.queryKey != job.queryKey) {
|
||||
disposeQuery();
|
||||
query.value = Query.fromOptions(
|
||||
job,
|
||||
externalData: externalData,
|
||||
queryBowl: queryBowl,
|
||||
);
|
||||
init();
|
||||
} else if (oldExternalData != null &&
|
||||
externalData != null &&
|
||||
!isShallowEqual(oldExternalData, externalData)) {
|
||||
QueryBowl.of(context).fetchQuery(
|
||||
@@ -68,16 +82,16 @@ Query<T, Outside> useQuery<T extends Object, Outside>({
|
||||
);
|
||||
} else {
|
||||
if (oldOnData != onData && oldOnData != null) {
|
||||
query.onDataListeners.remove(oldOnData);
|
||||
if (onData != null) query.onDataListeners.add(onData);
|
||||
query.value.onDataListeners.remove(oldOnData);
|
||||
if (onData != null) query.value.onDataListeners.add(onData);
|
||||
}
|
||||
if (oldOnError != onError && oldOnError != null) {
|
||||
query.onErrorListeners.remove(oldOnError);
|
||||
if (onError != null) query.onErrorListeners.add(onError);
|
||||
query.value.onErrorListeners.remove(oldOnError);
|
||||
if (onError != null) query.value.onErrorListeners.add(onError);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
return queryBowl.getQuery<T, Outside>(job.queryKey) ?? query;
|
||||
return queryBowl.getQuery<T, Outside>(job.queryKey) ?? query.value;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,11 @@ import 'dart:async';
|
||||
|
||||
import 'package:fl_query/src/base_operation.dart';
|
||||
import 'package:fl_query/src/models/mutation_job.dart';
|
||||
import 'package:fl_query/src/models/query_job.dart';
|
||||
import 'package:fl_query/src/query.dart';
|
||||
import 'package:fl_query/src/utils.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
enum MutationStatus {
|
||||
error,
|
||||
@@ -13,7 +17,8 @@ enum MutationStatus {
|
||||
|
||||
typedef MutationListener<T> = FutureOr<void> Function(T);
|
||||
|
||||
typedef MutationTaskFunction<T, V> = FutureOr<T> Function(String, V);
|
||||
typedef MutationTaskFunction<T, V> = FutureOr<T> Function(
|
||||
String queryKey, V variables);
|
||||
|
||||
class Mutation<T extends Object, V> extends BaseOperation<T, MutationStatus> {
|
||||
// all params
|
||||
@@ -32,6 +37,7 @@ class Mutation<T extends Object, V> extends BaseOperation<T, MutationStatus> {
|
||||
required this.task,
|
||||
required super.retries,
|
||||
required super.retryDelay,
|
||||
required super.queryBowl,
|
||||
required Duration cacheTime,
|
||||
MutationListener<T>? onData,
|
||||
MutationListener<dynamic>? onError,
|
||||
@@ -47,6 +53,7 @@ class Mutation<T extends Object, V> extends BaseOperation<T, MutationStatus> {
|
||||
MutationListener<T>? onData,
|
||||
MutationListener<dynamic>? onError,
|
||||
MutationListener<V>? onMutate,
|
||||
required super.queryBowl,
|
||||
}) : mutationKey = options.mutationKey,
|
||||
task = options.task,
|
||||
super(
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:fl_query/src/utils.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class MutationBuilder<T extends Object, V> extends StatefulWidget {
|
||||
final Function(BuildContext, Mutation<T, V>) builder;
|
||||
final Function(BuildContext context, Mutation<T, V> mutation) builder;
|
||||
final MutationJob<T, V> job;
|
||||
|
||||
/// Called when the query returns new data, on query
|
||||
@@ -39,60 +39,72 @@ class _MutationBuilderState<T extends Object, V>
|
||||
|
||||
late ValueKey<String> uKey;
|
||||
|
||||
late Mutation<T, V> mutation;
|
||||
Mutation<T, V>? mutation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
uKey = ValueKey<String>(uuid.v4());
|
||||
mutation = Mutation<T, V>.fromOptions(widget.job);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
queryBowl = QueryBowl.of(context);
|
||||
mutation = queryBowl.addMutation<T, V>(
|
||||
mutation,
|
||||
onData: widget.onData,
|
||||
onError: widget.onError,
|
||||
onMutate: widget.onMutate,
|
||||
key: uKey,
|
||||
);
|
||||
});
|
||||
WidgetsBinding.instance.addPostFrameCallback(init);
|
||||
}
|
||||
|
||||
void init([_]) {
|
||||
queryBowl = QueryBowl.of(context);
|
||||
mutation = queryBowl.addMutation<T, V>(
|
||||
Mutation<T, V>.fromOptions(widget.job, queryBowl: queryBowl),
|
||||
onData: widget.onData,
|
||||
onError: widget.onError,
|
||||
onMutate: widget.onMutate,
|
||||
key: uKey,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant MutationBuilder<T, V> oldWidget) {
|
||||
if (oldWidget.onData != widget.onData && oldWidget.onData != null) {
|
||||
mutation.onDataListeners.remove(oldWidget.onData);
|
||||
if (widget.onData != null) mutation.onDataListeners.add(widget.onData!);
|
||||
}
|
||||
if (oldWidget.onError != widget.onError && oldWidget.onError != null) {
|
||||
mutation.onErrorListeners.remove(oldWidget.onError);
|
||||
if (widget.onError != null)
|
||||
mutation.onErrorListeners.add(widget.onError!);
|
||||
}
|
||||
if (oldWidget.onMutate != widget.onMutate && oldWidget.onMutate != null) {
|
||||
mutation.onMutateListeners.remove(oldWidget.onMutate);
|
||||
if (widget.onMutate != null)
|
||||
mutation.onMutateListeners.add(widget.onMutate!);
|
||||
if (oldWidget.job.mutationKey != widget.job.mutationKey) {
|
||||
_mutationDispose();
|
||||
init();
|
||||
} else {
|
||||
if (oldWidget.onData != widget.onData && oldWidget.onData != null) {
|
||||
mutation?.onDataListeners.remove(oldWidget.onData);
|
||||
if (widget.onData != null)
|
||||
mutation?.onDataListeners.add(widget.onData!);
|
||||
}
|
||||
if (oldWidget.onError != widget.onError && oldWidget.onError != null) {
|
||||
mutation?.onErrorListeners.remove(oldWidget.onError);
|
||||
if (widget.onError != null)
|
||||
mutation?.onErrorListeners.add(widget.onError!);
|
||||
}
|
||||
if (oldWidget.onMutate != widget.onMutate && oldWidget.onMutate != null) {
|
||||
mutation?.onMutateListeners.remove(oldWidget.onMutate);
|
||||
if (widget.onMutate != null)
|
||||
mutation?.onMutateListeners.add(widget.onMutate!);
|
||||
}
|
||||
}
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
mutation.unmount(uKey);
|
||||
if (widget.onData != null) mutation.onDataListeners.remove(widget.onData);
|
||||
if (widget.onError != null)
|
||||
mutation.onErrorListeners.remove(widget.onError);
|
||||
if (widget.onMutate != null)
|
||||
mutation.onMutateListeners.remove(widget.onMutate);
|
||||
_mutationDispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _mutationDispose() {
|
||||
mutation?.unmount(uKey);
|
||||
if (widget.onData != null) mutation?.onDataListeners.remove(widget.onData);
|
||||
if (widget.onError != null)
|
||||
mutation?.onErrorListeners.remove(widget.onError);
|
||||
if (widget.onMutate != null)
|
||||
mutation?.onMutateListeners.remove(widget.onMutate);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
queryBowl = QueryBowl.of(context);
|
||||
final latestMutation =
|
||||
queryBowl.getMutation<T, V>(mutation.mutationKey) ?? mutation;
|
||||
queryBowl.getMutation<T, V>(widget.job.mutationKey) ?? mutation;
|
||||
if (latestMutation == null) return Container();
|
||||
return widget.builder(context, latestMutation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,8 +73,6 @@ class Query<T extends Object, Outside> extends BaseOperation<T, QueryStatus> {
|
||||
|
||||
Timer? _refetchIntervalTimer;
|
||||
|
||||
final QueryBowl queryBowl;
|
||||
|
||||
Query({
|
||||
required this.queryKey,
|
||||
required this.task,
|
||||
@@ -83,7 +81,7 @@ class Query<T extends Object, Outside> extends BaseOperation<T, QueryStatus> {
|
||||
required Outside externalData,
|
||||
required super.retries,
|
||||
required super.retryDelay,
|
||||
required this.queryBowl,
|
||||
required super.queryBowl,
|
||||
this.refetchOnMount,
|
||||
this.refetchOnReconnect,
|
||||
this.refetchInterval,
|
||||
@@ -108,7 +106,7 @@ class Query<T extends Object, Outside> extends BaseOperation<T, QueryStatus> {
|
||||
|
||||
Query.fromOptions(
|
||||
QueryJob<T, Outside> options, {
|
||||
required this.queryBowl,
|
||||
required super.queryBowl,
|
||||
required Outside externalData,
|
||||
QueryListener<T>? onData,
|
||||
QueryListener<dynamic>? onError,
|
||||
@@ -209,12 +207,11 @@ class Query<T extends Object, Outside> extends BaseOperation<T, QueryStatus> {
|
||||
}
|
||||
|
||||
Future<T?> fetch() async {
|
||||
if (!enabled) return null;
|
||||
|
||||
/// if isLoading/isRefetching is true that means its already fetching/
|
||||
/// refetching. So [_execute] again can create a race condition
|
||||
if (!enabled || isLoading || isRefetching) return null;
|
||||
if (hasData) {
|
||||
return data;
|
||||
}
|
||||
if (isLoading || isRefetching || hasData) return data;
|
||||
status = QueryStatus.loading;
|
||||
notifyListeners();
|
||||
return _execute().then((_) {
|
||||
@@ -226,12 +223,12 @@ class Query<T extends Object, Outside> extends BaseOperation<T, QueryStatus> {
|
||||
Future<T?> refetch() async {
|
||||
/// if isLoading/isRefetching is true that means its already fetching/
|
||||
/// refetching. So [_execute] again can create a race condition
|
||||
if (isRefetching || isLoading) return null;
|
||||
if (isRefetching || isLoading) return data;
|
||||
if (enabled && !fetched) await fetch();
|
||||
status = QueryStatus.refetching;
|
||||
refetchCount++;
|
||||
// disabling the lazy query bound when query was actually called
|
||||
if (!enabled) enabled = true;
|
||||
if (enabled && !fetched) await fetch();
|
||||
notifyListeners();
|
||||
return await _execute().then((_) => data);
|
||||
}
|
||||
@@ -247,11 +244,7 @@ class Query<T extends Object, Outside> extends BaseOperation<T, QueryStatus> {
|
||||
/// immutability
|
||||
void setQueryData(QueryUpdateFunction<T> updateFn) async {
|
||||
final newData = await updateFn(data);
|
||||
if (data == newData) {
|
||||
// TODO: Better Error handling & Error structure
|
||||
throw Exception(
|
||||
"[fl_query] new instance of data should be returned because of immutability");
|
||||
}
|
||||
if (data == newData) return;
|
||||
data = newData;
|
||||
status = QueryStatus.success;
|
||||
notifyListeners();
|
||||
@@ -269,11 +262,6 @@ class Query<T extends Object, Outside> extends BaseOperation<T, QueryStatus> {
|
||||
fetched = false;
|
||||
status = QueryStatus.idle;
|
||||
retryAttempts = 0;
|
||||
for (final queryEntry in _dependencyQueries.entries) {
|
||||
queryEntry.value.unmount(queryEntry.key);
|
||||
queryEntry.value.removeListener(refetch);
|
||||
}
|
||||
_dependencyQueries = {};
|
||||
onDataListeners.clear();
|
||||
onErrorListeners.clear();
|
||||
mounts.clear();
|
||||
@@ -306,40 +294,8 @@ class Query<T extends Object, Outside> extends BaseOperation<T, QueryStatus> {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Map<ValueKey<String>, Query> _dependencyQueries = {};
|
||||
|
||||
/// only usable inside [QueryJob.task] method
|
||||
Query<T, Outside> dependOnQuery<T extends Object, Outside>(
|
||||
QueryJob<T, Outside> job, {
|
||||
required Outside externalData,
|
||||
}) {
|
||||
final key = ValueKey(uuid.v4());
|
||||
final query = queryBowl.addQuery(
|
||||
Query<T, Outside>.fromOptions(
|
||||
job,
|
||||
externalData: externalData,
|
||||
queryBowl: queryBowl,
|
||||
),
|
||||
key: key,
|
||||
);
|
||||
// removing listener if it was already hooked to it previously
|
||||
query.removeListener(refetch);
|
||||
query.addListener(refetch);
|
||||
final uKey =
|
||||
_dependencyQueries.keys.firstWhereOrNull((k) => k.value == key.value) ??
|
||||
key;
|
||||
_dependencyQueries[uKey] = query;
|
||||
if (!query.fetched) query.fetch();
|
||||
return query;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
for (final queryEntry in _dependencyQueries.entries) {
|
||||
queryEntry.value.unmount(queryEntry.key);
|
||||
queryEntry.value.removeListener(refetch);
|
||||
}
|
||||
super.dispose();
|
||||
Future<T?> _internalRefetch<X>(X dataOrError) {
|
||||
return refetch();
|
||||
}
|
||||
|
||||
bool get isStale {
|
||||
|
||||
@@ -74,7 +74,7 @@ class _QueryBowlScopeState extends State<QueryBowlScope> {
|
||||
.listen((ConnectivityResult result) async {
|
||||
if (isConnectedToInternet(result)) {
|
||||
for (final query in queries) {
|
||||
if (query.refetchOnReconnect == false) continue;
|
||||
if (query.refetchOnReconnect == false || !query.enabled) continue;
|
||||
await query.refetch();
|
||||
await Future.delayed(widget.refetchOnReconnectDelay);
|
||||
}
|
||||
@@ -108,75 +108,73 @@ class _QueryBowlScopeState extends State<QueryBowlScope> {
|
||||
}
|
||||
|
||||
void updateQueries(Query query) {
|
||||
// checking & not including inactive queries
|
||||
// basically garbage collecting queries
|
||||
if (query.isInactive) {
|
||||
/// there's a bug currently,
|
||||
/// where if somehow a [Query] (queryA) is depending on another
|
||||
/// [Query] (queryB) & queryA has become inactive so its getting
|
||||
/// disposed but at the same moment queryB got refetched will make
|
||||
/// queryB's [BaseOperation.unmount] to throw [RangeError] for
|
||||
/// calling [ChangeNotifier.notifyListener] after [cacheDelay]
|
||||
/// inside [Timer.periodic] callback
|
||||
///
|
||||
/// To mitigate this, [Query.reset] is used instead of using
|
||||
/// [Query.dispose] as it doesn't call [super.dispose]
|
||||
query.reset();
|
||||
}
|
||||
|
||||
setState(() {
|
||||
queries = Set.from(
|
||||
query.isInactive
|
||||
? queries.where((el) => el.queryKey != query.queryKey)
|
||||
: queries,
|
||||
);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// checking & not including inactive queries
|
||||
// basically garbage collecting queries
|
||||
setState(() {
|
||||
queries = Set.from(
|
||||
query.isInactive
|
||||
? queries.where((el) => el.queryKey != query.queryKey)
|
||||
: queries,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void updateMutations(Mutation mutation) {
|
||||
setState(() {
|
||||
// checking & not including inactive mutations
|
||||
// basically garbage collecting mutations
|
||||
mutations = Set.from(
|
||||
mutation.isInactive
|
||||
? mutations.where(
|
||||
(el) => el.mutationKey != mutation.mutationKey,
|
||||
)
|
||||
: mutations,
|
||||
);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
setState(() {
|
||||
// checking & not including inactive mutations
|
||||
// basically garbage collecting mutations
|
||||
mutations = Set.from(
|
||||
mutation.isInactive
|
||||
? mutations.where(
|
||||
(el) => el.mutationKey != mutation.mutationKey,
|
||||
)
|
||||
: mutations,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void addQuery<T extends Object, Outside>(Query<T, Outside> query) {
|
||||
setState(() {
|
||||
queries = Set.from({...queries, query});
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
setState(() {
|
||||
queries = Set.from({...queries, query});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void addMutation<T extends Object, V>(Mutation<T, V> mutation) {
|
||||
setState(() {
|
||||
mutations = Set.from({...mutations, mutation});
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
setState(() {
|
||||
mutations = Set.from({...mutations, mutation});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
int removeQueries(List<String> queryKeys) {
|
||||
int count = 0;
|
||||
setState(() {
|
||||
mutations = Set.from(
|
||||
queries.whereNot((query) {
|
||||
final isAboutToRip = queryKeys.contains(query.queryKey);
|
||||
if (isAboutToRip) count++;
|
||||
return isAboutToRip;
|
||||
}),
|
||||
);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
setState(() {
|
||||
mutations = Set.from(
|
||||
queries.whereNot((query) {
|
||||
final isAboutToRip = queryKeys.contains(query.queryKey);
|
||||
if (isAboutToRip) count++;
|
||||
return isAboutToRip;
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
setState(() {
|
||||
queries = Set<Query>();
|
||||
mutations = Set<Mutation>();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
setState(() {
|
||||
queries = Set<Query>();
|
||||
mutations = Set<Mutation>();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -270,9 +268,7 @@ class QueryBowl extends InheritedWidget {
|
||||
if (onError != null) prevQuery.onErrorListeners.add(onError);
|
||||
if (!prevQuery.hasData || hasExternalDataChanged) {
|
||||
if (hasExternalDataChanged) prevQuery.setExternalData(externalData);
|
||||
return prevQuery.fetched
|
||||
? await prevQuery.refetch()
|
||||
: await prevQuery.fetch();
|
||||
return await prevQuery.refetch();
|
||||
}
|
||||
// mounting the widget that is using the query in the prevQuery
|
||||
return prevQuery.data;
|
||||
@@ -353,6 +349,9 @@ class QueryBowl extends InheritedWidget {
|
||||
prevMutation.mount(key);
|
||||
return prevMutation;
|
||||
} else {
|
||||
if (onData != null) mutation.onDataListeners.add(onData);
|
||||
if (onError != null) mutation.onErrorListeners.add(onError);
|
||||
if (onMutate != null) mutation.onMutateListeners.add(onMutate);
|
||||
mutation.updateDefaultOptions(cacheTime: cacheTime);
|
||||
mutation.mount(key);
|
||||
_addMutation(mutation);
|
||||
@@ -413,8 +412,9 @@ class QueryBowl extends InheritedWidget {
|
||||
|
||||
Future<void> refetchQueries(List<String> queryKeys) async {
|
||||
for (final query in _queries) {
|
||||
if (!queryKeys.contains(query.queryKey)) continue;
|
||||
await query.refetch();
|
||||
if (queryKeys.contains(query.queryKey)) {
|
||||
await query.refetch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:fl_query/src/utils.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class QueryBuilder<T extends Object, Outside> extends StatefulWidget {
|
||||
final Function(BuildContext, Query<T, Outside>) builder;
|
||||
final Function(BuildContext context, Query<T, Outside> query) builder;
|
||||
final QueryJob<T, Outside> job;
|
||||
final Outside externalData;
|
||||
|
||||
@@ -40,30 +40,36 @@ class _QueryBuilderState<T extends Object, Outside>
|
||||
void initState() {
|
||||
super.initState();
|
||||
uKey = ValueKey<String>(uuid.v4());
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
query = QueryBowl.of(context).addQuery<T, Outside>(
|
||||
Query<T, Outside>.fromOptions(
|
||||
widget.job,
|
||||
externalData: widget.externalData,
|
||||
queryBowl: QueryBowl.of(context),
|
||||
),
|
||||
key: uKey,
|
||||
onData: widget.onData,
|
||||
onError: widget.onError,
|
||||
);
|
||||
final hasExternalDataChanged = query!.externalData != null &&
|
||||
query!.prevUsedExternalData != null &&
|
||||
!isShallowEqual(query!.externalData!, query!.prevUsedExternalData!);
|
||||
(query!.fetched && query!.refetchOnMount == true) ||
|
||||
hasExternalDataChanged
|
||||
? await query!.refetch()
|
||||
: await query!.fetch();
|
||||
});
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => init());
|
||||
}
|
||||
|
||||
void init([QueryBowl? bowl]) async {
|
||||
bowl ??= QueryBowl.of(context);
|
||||
query = bowl.addQuery<T, Outside>(
|
||||
Query<T, Outside>.fromOptions(
|
||||
widget.job,
|
||||
externalData: widget.externalData,
|
||||
queryBowl: QueryBowl.of(context),
|
||||
),
|
||||
key: uKey,
|
||||
onData: widget.onData,
|
||||
onError: widget.onError,
|
||||
);
|
||||
final hasExternalDataChanged = query!.externalData != null &&
|
||||
query!.prevUsedExternalData != null &&
|
||||
!isShallowEqual(query!.externalData!, query!.prevUsedExternalData!);
|
||||
(query!.fetched && query!.refetchOnMount == true) || hasExternalDataChanged
|
||||
? await query!.refetch()
|
||||
: await query!.fetch();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant oldWidget) {
|
||||
if (oldWidget.externalData != null &&
|
||||
// re-init the query-builder when new queryJob is appended
|
||||
if (oldWidget.job.queryKey != widget.job.queryKey) {
|
||||
_queryDispose();
|
||||
init();
|
||||
} else if (oldWidget.externalData != null &&
|
||||
widget.externalData != null &&
|
||||
!isShallowEqual(oldWidget.externalData!, widget.externalData!)) {
|
||||
QueryBowl.of(context).fetchQuery(
|
||||
@@ -87,11 +93,15 @@ class _QueryBuilderState<T extends Object, Outside>
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_queryDispose() {
|
||||
query?.unmount(uKey);
|
||||
if (widget.onData != null) query?.onDataListeners.remove(widget.onData);
|
||||
if (widget.onError != null) query?.onErrorListeners.remove(widget.onError);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_queryDispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user