Depended queries support added
This commit is contained in:
@@ -0,0 +1,36 @@
|
|||||||
|
import 'package:example/main.dart';
|
||||||
|
import 'package:fl_query/fl_query.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
final dependentQueryJob = QueryJob<String, void>(
|
||||||
|
queryKey: "dependent-query-job",
|
||||||
|
task: (queryKey, externalData, query) {
|
||||||
|
final successQuery =
|
||||||
|
query.dependOnQuery<String, void>(successJob, externalData: null);
|
||||||
|
final failedQuery =
|
||||||
|
query.dependOnQuery<String, void>(failedJob, externalData: null);
|
||||||
|
if (failedQuery.hasError) return failedQuery.error.toString();
|
||||||
|
if (successQuery.hasData) return successQuery.data!;
|
||||||
|
return "No data from success query yet";
|
||||||
|
});
|
||||||
|
|
||||||
|
class DependentQueryExample extends StatelessWidget {
|
||||||
|
const DependentQueryExample({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(),
|
||||||
|
body: QueryBuilder<String, void>(
|
||||||
|
job: dependentQueryJob,
|
||||||
|
externalData: null,
|
||||||
|
builder: (context, query) {
|
||||||
|
if (query.isLoading || query.isRefetching || !query.hasData) {
|
||||||
|
return const CircularProgressIndicator();
|
||||||
|
}
|
||||||
|
return Text(query.data!);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
|||||||
final lazyQueryJob = QueryJob<String, String>(
|
final lazyQueryJob = QueryJob<String, String>(
|
||||||
queryKey: "non_enabled_query",
|
queryKey: "non_enabled_query",
|
||||||
enabled: false,
|
enabled: false,
|
||||||
task: (queryKey, data) {
|
task: (queryKey, data, _) {
|
||||||
return Future.delayed(const Duration(milliseconds: 500),
|
return Future.delayed(const Duration(milliseconds: 500),
|
||||||
() => "Hello from $queryKey with $data");
|
() => "Hello from $queryKey with $data");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:example/another_component.dart';
|
import 'package:example/another_component.dart';
|
||||||
|
import 'package:example/dependent_query_example.dart';
|
||||||
import 'package:example/hooks_example.dart';
|
import 'package:example/hooks_example.dart';
|
||||||
import 'package:example/lazy_query.dart';
|
import 'package:example/lazy_query.dart';
|
||||||
import 'package:example/mutation_example.dart';
|
import 'package:example/mutation_example.dart';
|
||||||
@@ -34,13 +35,13 @@ class MyApp extends StatelessWidget {
|
|||||||
|
|
||||||
final successJob = QueryJob<String, void>(
|
final successJob = QueryJob<String, void>(
|
||||||
queryKey: "greetings",
|
queryKey: "greetings",
|
||||||
task: (queryKey, _) => Future.delayed(const Duration(seconds: 2),
|
task: (queryKey, _, __) => Future.delayed(const Duration(seconds: 2),
|
||||||
() => "Welcome ($queryKey) ${Random.secure().nextInt(100)}"),
|
() => "Welcome ($queryKey) ${Random.secure().nextInt(100)}"),
|
||||||
);
|
);
|
||||||
|
|
||||||
final failedJob = QueryJob<String, void>(
|
final failedJob = QueryJob<String, void>(
|
||||||
queryKey: "failure",
|
queryKey: "failure",
|
||||||
task: (queryKey, _) => Random().nextBool()
|
task: (queryKey, _, __) => Random().nextBool()
|
||||||
? Future.error("[$queryKey] Failed for unknown reason")
|
? Future.error("[$queryKey] Failed for unknown reason")
|
||||||
: Future.value(
|
: Future.value(
|
||||||
"Success, you'll get slowly ${Random().nextInt(100)}!",
|
"Success, you'll get slowly ${Random().nextInt(100)}!",
|
||||||
@@ -178,6 +179,16 @@ class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
child: const Text("Dependent Query Example"),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const DependentQueryExample(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
const AnotherComponent(),
|
const AnotherComponent(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'package:flutter/material.dart';
|
|||||||
final jobWithExternalData = QueryJob<String, String>(
|
final jobWithExternalData = QueryJob<String, String>(
|
||||||
queryKey: "external_data",
|
queryKey: "external_data",
|
||||||
cacheTime: const Duration(seconds: 10),
|
cacheTime: const Duration(seconds: 10),
|
||||||
task: (queryKey, data) {
|
task: (queryKey, data, _) {
|
||||||
return Future.delayed(const Duration(milliseconds: 500),
|
return Future.delayed(const Duration(milliseconds: 500),
|
||||||
() => "Hello from $queryKey with $data");
|
() => "Hello from $queryKey with $data");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -60,6 +60,6 @@ abstract class BaseOperation<Data, StatusType> extends ChangeNotifier {
|
|||||||
bool get isLoading;
|
bool get isLoading;
|
||||||
bool get isIdle;
|
bool get isIdle;
|
||||||
bool get isInactive => mounts.isEmpty;
|
bool get isInactive => mounts.isEmpty;
|
||||||
bool get hasData => isSuccess && data != null;
|
bool get hasData => data != null;
|
||||||
bool get hasError => isError && error != null;
|
bool get hasError => error != null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,106 +17,67 @@ Query<T, Outside> useQuery<T extends Object, Outside>({
|
|||||||
QueryListener<dynamic>? onError,
|
QueryListener<dynamic>? onError,
|
||||||
List<Object?>? keys,
|
List<Object?>? keys,
|
||||||
}) {
|
}) {
|
||||||
return use(_UseQuery<T, Outside>(
|
final context = useContext();
|
||||||
externalData: externalData,
|
QueryBowl queryBowl = QueryBowl.of(context);
|
||||||
job: job,
|
final ValueKey<String> uKey = useMemoized(() => ValueKey(uuid.v4()), []);
|
||||||
onData: onData,
|
Query<T, Outside> query = useMemoized(
|
||||||
onError: onError,
|
() => Query.fromOptions(
|
||||||
keys: keys,
|
job,
|
||||||
));
|
externalData: externalData,
|
||||||
}
|
queryBowl: queryBowl,
|
||||||
|
onData: onData,
|
||||||
|
onError: onError,
|
||||||
|
),
|
||||||
|
[]);
|
||||||
|
|
||||||
class _UseQuery<T extends Object, Outside> extends Hook<Query<T, Outside>> {
|
final oldExternalData = usePrevious(externalData);
|
||||||
final QueryJob<T, Outside> job;
|
final oldOnData = usePrevious(onData);
|
||||||
final Outside externalData;
|
final oldOnError = usePrevious(onError);
|
||||||
|
|
||||||
/// Called when the query returns new data, on query
|
useEffect(() {
|
||||||
/// refetch or query gets expired
|
queryBowl.addQuery<T, Outside>(
|
||||||
final QueryListener<T>? onData;
|
query,
|
||||||
|
key: uKey,
|
||||||
/// Called when the query returns error
|
onData: onData,
|
||||||
final QueryListener<dynamic>? onError;
|
onError: onError,
|
||||||
const _UseQuery({
|
|
||||||
required this.job,
|
|
||||||
required this.externalData,
|
|
||||||
this.onData,
|
|
||||||
this.onError,
|
|
||||||
super.keys,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
HookState<Query<T, Outside>, Hook<Query<T, Outside>>> createState() =>
|
|
||||||
_UseQueryHookState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _UseQueryHookState<T extends Object, Outside>
|
|
||||||
extends HookState<Query<T, Outside>, _UseQuery<T, Outside>> {
|
|
||||||
late QueryBowl queryBowl;
|
|
||||||
late final ValueKey<String> uKey;
|
|
||||||
late Query<T, Outside> query;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initHook() {
|
|
||||||
super.initHook();
|
|
||||||
uKey = ValueKey<String>(uuid.v4());
|
|
||||||
query = Query<T, Outside>.fromOptions(
|
|
||||||
hook.job,
|
|
||||||
externalData: hook.externalData,
|
|
||||||
);
|
);
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
final hasExternalDataChanged = query.externalData != null &&
|
||||||
query = QueryBowl.of(context).addQuery<T, Outside>(
|
query.prevUsedExternalData != null &&
|
||||||
query,
|
!isShallowEqual(query.externalData!, query.prevUsedExternalData!);
|
||||||
key: uKey,
|
(query.fetched && query.refetchOnMount == true) || hasExternalDataChanged
|
||||||
onData: hook.onData,
|
? query.refetch()
|
||||||
onError: hook.onError,
|
: query.fetch();
|
||||||
);
|
|
||||||
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
|
return () {
|
||||||
void didUpdateHook(_UseQuery<T, Outside> oldHook) {
|
query.unmount(uKey);
|
||||||
if (oldHook.externalData != null &&
|
if (onData != null) query.onDataListeners.remove(onData);
|
||||||
hook.externalData != null &&
|
if (onError != null) query.onErrorListeners.remove(onError);
|
||||||
!isShallowEqual(oldHook.externalData!, hook.externalData!)) {
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
if (oldExternalData != null &&
|
||||||
|
externalData != null &&
|
||||||
|
!isShallowEqual(oldExternalData, externalData)) {
|
||||||
QueryBowl.of(context).fetchQuery(
|
QueryBowl.of(context).fetchQuery(
|
||||||
hook.job,
|
job,
|
||||||
externalData: hook.externalData,
|
externalData: externalData,
|
||||||
onData: hook.onData,
|
onData: onData,
|
||||||
onError: hook.onError,
|
onError: onError,
|
||||||
key: uKey,
|
key: uKey,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if (oldHook.onData != hook.onData && oldHook.onData != null) {
|
if (oldOnData != onData && oldOnData != null) {
|
||||||
query.onDataListeners.remove(oldHook.onData);
|
query.onDataListeners.remove(oldOnData);
|
||||||
if (hook.onData != null) query.onDataListeners.add(hook.onData!);
|
if (onData != null) query.onDataListeners.add(onData);
|
||||||
}
|
}
|
||||||
if (oldHook.onError != hook.onError && oldHook.onError != null) {
|
if (oldOnError != onError && oldOnError != null) {
|
||||||
query.onErrorListeners.remove(oldHook.onError);
|
query.onErrorListeners.remove(oldOnError);
|
||||||
if (hook.onError != null) query.onErrorListeners.add(hook.onError!);
|
if (onError != null) query.onErrorListeners.add(onError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super.didUpdateHook(oldHook);
|
return null;
|
||||||
}
|
});
|
||||||
|
|
||||||
@override
|
return queryBowl.getQuery<T, Outside>(job.queryKey) ?? query;
|
||||||
void dispose() {
|
|
||||||
query.unmount(uKey);
|
|
||||||
if (hook.onData != null) query.onDataListeners.remove(hook.onData);
|
|
||||||
if (hook.onError != null) query.onErrorListeners.remove(hook.onError);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Query<T, Outside> build(BuildContext context) {
|
|
||||||
queryBowl = QueryBowl.of(context);
|
|
||||||
return queryBowl.getQuery<T, Outside>(query.queryKey) ?? query;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get debugLabel => 'useQuery';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,22 @@
|
|||||||
import 'package:fl_query/src/query.dart';
|
import 'package:fl_query/src/query.dart';
|
||||||
|
|
||||||
|
/// How to make dependent Queries?
|
||||||
|
///
|
||||||
|
/// Pass a [QueryBowl] class/object to [task] that contains a method
|
||||||
|
/// [QueryBowl.dependOnQuery] that takes a [QueryJob] and uses that to get
|
||||||
|
/// the appropriate query for it or if it doesn't exist creates a new
|
||||||
|
/// instance. It listens to the changes of [Query] of the passed
|
||||||
|
/// [QueryJob] & calls the [notifyListener] method of running [Query]
|
||||||
|
///
|
||||||
|
/// If shown briefly
|
||||||
|
///
|
||||||
|
/// ```data
|
||||||
|
/// task: (queryKey, external, queryBowl){
|
||||||
|
/// final dependentQuery = queryBowl.dependOnQuery(dependentQueryJob);
|
||||||
|
/// return someAsyncTask();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
class QueryJob<T extends Object, Outside> {
|
class QueryJob<T extends Object, Outside> {
|
||||||
// all params
|
// all params
|
||||||
String _queryKey;
|
String _queryKey;
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:fl_query/src/base_operation.dart';
|
import 'package:fl_query/src/base_operation.dart';
|
||||||
import 'package:fl_query/src/models/query_job.dart';
|
import 'package:fl_query/src/models/query_job.dart';
|
||||||
|
import 'package:fl_query/src/query_bowl.dart';
|
||||||
|
import 'package:fl_query/src/utils.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
enum QueryStatus {
|
enum QueryStatus {
|
||||||
/// in times when an error occurs
|
/// in times when an error occurs
|
||||||
@@ -23,7 +26,11 @@ enum QueryStatus {
|
|||||||
refetching;
|
refetching;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef QueryTaskFunction<T, Outside> = FutureOr<T> Function(String, Outside);
|
typedef QueryTaskFunction<T extends Object, Outside> = FutureOr<T> Function(
|
||||||
|
String queryKey,
|
||||||
|
Outside externalData,
|
||||||
|
Query<T, Outside> self,
|
||||||
|
);
|
||||||
|
|
||||||
typedef QueryListener<T> = FutureOr<void> Function(T);
|
typedef QueryListener<T> = FutureOr<void> Function(T);
|
||||||
|
|
||||||
@@ -64,6 +71,8 @@ class Query<T extends Object, Outside> extends BaseOperation<T, QueryStatus> {
|
|||||||
|
|
||||||
Timer? _refetchIntervalTimer;
|
Timer? _refetchIntervalTimer;
|
||||||
|
|
||||||
|
final QueryBowl queryBowl;
|
||||||
|
|
||||||
Query({
|
Query({
|
||||||
required this.queryKey,
|
required this.queryKey,
|
||||||
required this.task,
|
required this.task,
|
||||||
@@ -72,6 +81,7 @@ class Query<T extends Object, Outside> extends BaseOperation<T, QueryStatus> {
|
|||||||
required Outside externalData,
|
required Outside externalData,
|
||||||
required super.retries,
|
required super.retries,
|
||||||
required super.retryDelay,
|
required super.retryDelay,
|
||||||
|
required this.queryBowl,
|
||||||
this.refetchOnMount,
|
this.refetchOnMount,
|
||||||
this.refetchInterval,
|
this.refetchInterval,
|
||||||
this.enabled = true,
|
this.enabled = true,
|
||||||
@@ -95,6 +105,7 @@ class Query<T extends Object, Outside> extends BaseOperation<T, QueryStatus> {
|
|||||||
|
|
||||||
Query.fromOptions(
|
Query.fromOptions(
|
||||||
QueryJob<T, Outside> options, {
|
QueryJob<T, Outside> options, {
|
||||||
|
required this.queryBowl,
|
||||||
required Outside externalData,
|
required Outside externalData,
|
||||||
QueryListener<T>? onData,
|
QueryListener<T>? onData,
|
||||||
QueryListener<dynamic>? onError,
|
QueryListener<dynamic>? onError,
|
||||||
@@ -136,7 +147,11 @@ class Query<T extends Object, Outside> extends BaseOperation<T, QueryStatus> {
|
|||||||
Future<void> _execute() async {
|
Future<void> _execute() async {
|
||||||
try {
|
try {
|
||||||
retryAttempts = 0;
|
retryAttempts = 0;
|
||||||
data = await task(queryKey, _externalData);
|
data = await task(
|
||||||
|
queryKey,
|
||||||
|
_externalData,
|
||||||
|
this,
|
||||||
|
);
|
||||||
_prevUsedExternalData = _externalData;
|
_prevUsedExternalData = _externalData;
|
||||||
updatedAt = DateTime.now();
|
updatedAt = DateTime.now();
|
||||||
status = QueryStatus.success;
|
status = QueryStatus.success;
|
||||||
@@ -157,7 +172,11 @@ class Query<T extends Object, Outside> extends BaseOperation<T, QueryStatus> {
|
|||||||
while (retryAttempts <= retries) {
|
while (retryAttempts <= retries) {
|
||||||
await Future.delayed(retryDelay);
|
await Future.delayed(retryDelay);
|
||||||
try {
|
try {
|
||||||
data = await task(queryKey, _externalData);
|
data = await task(
|
||||||
|
queryKey,
|
||||||
|
_externalData,
|
||||||
|
this,
|
||||||
|
);
|
||||||
_prevUsedExternalData = _externalData;
|
_prevUsedExternalData = _externalData;
|
||||||
status = QueryStatus.success;
|
status = QueryStatus.success;
|
||||||
for (final onData in onDataListeners) {
|
for (final onData in onDataListeners) {
|
||||||
@@ -182,7 +201,9 @@ class Query<T extends Object, Outside> extends BaseOperation<T, QueryStatus> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<T?> fetch() async {
|
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) {
|
if (hasData) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@@ -195,13 +216,14 @@ class Query<T extends Object, Outside> extends BaseOperation<T, QueryStatus> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<T?> refetch() async {
|
Future<T?> refetch() async {
|
||||||
// cannot let run multiple refetch at the same time. It can cause
|
/// if isLoading/isRefetching is true that means its already fetching/
|
||||||
// race-condition
|
/// refetching. So [_execute] again can create a race condition
|
||||||
if (isRefetching) return null;
|
if (isRefetching || isLoading) return null;
|
||||||
status = QueryStatus.refetching;
|
status = QueryStatus.refetching;
|
||||||
refetchCount++;
|
refetchCount++;
|
||||||
// disabling the lazy query bound when query was actually called
|
// disabling the lazy query bound when query was actually called
|
||||||
if (!enabled) enabled = false;
|
if (!enabled) enabled = true;
|
||||||
|
if (enabled && !fetched) await fetch();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return await _execute().then((_) => data);
|
return await _execute().then((_) => data);
|
||||||
}
|
}
|
||||||
@@ -239,6 +261,11 @@ class Query<T extends Object, Outside> extends BaseOperation<T, QueryStatus> {
|
|||||||
fetched = false;
|
fetched = false;
|
||||||
status = QueryStatus.idle;
|
status = QueryStatus.idle;
|
||||||
retryAttempts = 0;
|
retryAttempts = 0;
|
||||||
|
for (final queryEntry in _dependencyQueries.entries) {
|
||||||
|
queryEntry.value.unmount(queryEntry.key);
|
||||||
|
queryEntry.value.removeListener(refetch);
|
||||||
|
}
|
||||||
|
_dependencyQueries = {};
|
||||||
onDataListeners.clear();
|
onDataListeners.clear();
|
||||||
onErrorListeners.clear();
|
onErrorListeners.clear();
|
||||||
mounts.clear();
|
mounts.clear();
|
||||||
@@ -268,6 +295,42 @@ class Query<T extends Object, Outside> extends BaseOperation<T, QueryStatus> {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<ValueKey<String>, Query> _dependencyQueries = {};
|
||||||
|
|
||||||
|
/// only usable inside any 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();
|
||||||
|
}
|
||||||
|
|
||||||
bool get isStale {
|
bool get isStale {
|
||||||
/// when [_staleTime] is [Duration.zero], the query will always be
|
/// when [_staleTime] is [Duration.zero], the query will always be
|
||||||
/// stale & will never refetch in the background. But can be inactive
|
/// stale & will never refetch in the background. But can be inactive
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class QueryBowlScope extends StatefulWidget {
|
|||||||
const QueryBowlScope({
|
const QueryBowlScope({
|
||||||
required this.child,
|
required this.child,
|
||||||
this.staleTime = Duration.zero,
|
this.staleTime = Duration.zero,
|
||||||
this.cacheTime = const Duration(minutes: 5),
|
this.cacheTime = const Duration(seconds: 5),
|
||||||
this.refetchInterval = Duration.zero,
|
this.refetchInterval = Duration.zero,
|
||||||
this.refetchOnMount = false,
|
this.refetchOnMount = false,
|
||||||
this.refetchOnReconnect = true,
|
this.refetchOnReconnect = true,
|
||||||
@@ -79,9 +79,23 @@ class _QueryBowlScopeState extends State<QueryBowlScope> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void updateQueries(Query query) {
|
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(() {
|
setState(() {
|
||||||
// checking & not including inactive queries
|
|
||||||
// basically garbage collecting queries
|
|
||||||
queries = Set.from(
|
queries = Set.from(
|
||||||
query.isInactive
|
query.isInactive
|
||||||
? queries.where((el) => el.queryKey != query.queryKey)
|
? queries.where((el) => el.queryKey != query.queryKey)
|
||||||
@@ -240,6 +254,7 @@ class QueryBowl extends InheritedWidget {
|
|||||||
final query = Query<T, Outside>.fromOptions(
|
final query = Query<T, Outside>.fromOptions(
|
||||||
options,
|
options,
|
||||||
externalData: externalData,
|
externalData: externalData,
|
||||||
|
queryBowl: this,
|
||||||
onData: onData,
|
onData: onData,
|
||||||
onError: onError,
|
onError: onError,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -34,29 +34,30 @@ class _QueryBuilderState<T extends Object, Outside>
|
|||||||
extends State<QueryBuilder<T, Outside>> {
|
extends State<QueryBuilder<T, Outside>> {
|
||||||
late QueryBowl queryBowl;
|
late QueryBowl queryBowl;
|
||||||
late final ValueKey<String> uKey;
|
late final ValueKey<String> uKey;
|
||||||
late Query<T, Outside> query;
|
Query<T, Outside>? query;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
uKey = ValueKey<String>(uuid.v4());
|
uKey = ValueKey<String>(uuid.v4());
|
||||||
query = Query<T, Outside>.fromOptions(
|
|
||||||
widget.job,
|
|
||||||
externalData: widget.externalData,
|
|
||||||
);
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
query = QueryBowl.of(context).addQuery<T, Outside>(
|
query = QueryBowl.of(context).addQuery<T, Outside>(
|
||||||
query,
|
Query<T, Outside>.fromOptions(
|
||||||
|
widget.job,
|
||||||
|
externalData: widget.externalData,
|
||||||
|
queryBowl: QueryBowl.of(context),
|
||||||
|
),
|
||||||
key: uKey,
|
key: uKey,
|
||||||
onData: widget.onData,
|
onData: widget.onData,
|
||||||
onError: widget.onError,
|
onError: widget.onError,
|
||||||
);
|
);
|
||||||
final hasExternalDataChanged = query.externalData != null &&
|
final hasExternalDataChanged = query!.externalData != null &&
|
||||||
query.prevUsedExternalData != null &&
|
query!.prevUsedExternalData != null &&
|
||||||
!isShallowEqual(query.externalData!, query.prevUsedExternalData!);
|
!isShallowEqual(query!.externalData!, query!.prevUsedExternalData!);
|
||||||
(query.fetched && query.refetchOnMount == true) || hasExternalDataChanged
|
(query!.fetched && query!.refetchOnMount == true) ||
|
||||||
? await query.refetch()
|
hasExternalDataChanged
|
||||||
: await query.fetch();
|
? await query!.refetch()
|
||||||
|
: await query!.fetch();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,12 +75,13 @@ class _QueryBuilderState<T extends Object, Outside>
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if (oldWidget.onData != widget.onData && oldWidget.onData != null) {
|
if (oldWidget.onData != widget.onData && oldWidget.onData != null) {
|
||||||
query.onDataListeners.remove(oldWidget.onData);
|
query?.onDataListeners.remove(oldWidget.onData);
|
||||||
if (widget.onData != null) query.onDataListeners.add(widget.onData!);
|
if (widget.onData != null) query?.onDataListeners.add(widget.onData!);
|
||||||
}
|
}
|
||||||
if (oldWidget.onError != widget.onError && oldWidget.onError != null) {
|
if (oldWidget.onError != widget.onError && oldWidget.onError != null) {
|
||||||
query.onErrorListeners.remove(oldWidget.onError);
|
query?.onErrorListeners.remove(oldWidget.onError);
|
||||||
if (widget.onError != null) query.onErrorListeners.add(widget.onError!);
|
if (widget.onError != null)
|
||||||
|
query?.onErrorListeners.add(widget.onError!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
@@ -87,16 +89,18 @@ class _QueryBuilderState<T extends Object, Outside>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
query.unmount(uKey);
|
query?.unmount(uKey);
|
||||||
if (widget.onData != null) query.onDataListeners.remove(widget.onData);
|
if (widget.onData != null) query?.onDataListeners.remove(widget.onData);
|
||||||
if (widget.onError != null) query.onErrorListeners.remove(widget.onError);
|
if (widget.onError != null) query?.onErrorListeners.remove(widget.onError);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
queryBowl = QueryBowl.of(context);
|
queryBowl = QueryBowl.of(context);
|
||||||
final latestQuery = queryBowl.getQuery<T, Outside>(query.queryKey) ?? query;
|
final latestQuery =
|
||||||
|
queryBowl.getQuery<T, Outside>(widget.job.queryKey) ?? query;
|
||||||
|
if (latestQuery == null) return Container();
|
||||||
return widget.builder(context, latestQuery);
|
return widget.builder(context, latestQuery);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user