Internal API change
now query & mutation share same base class queries are now self-refetchable queryJobs & mutationJobs now support dynamic key allowing dynamic Query/Mutation creation query status & mutation status are now just on point & accurate
This commit is contained in:
@@ -53,7 +53,25 @@ class MyHomePage extends StatefulWidget {
|
|||||||
State<MyHomePage> createState() => _MyHomePageState();
|
State<MyHomePage> createState() => _MyHomePageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MyHomePageState extends State<MyHomePage> {
|
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
super.didChangeAppLifecycleState(state);
|
||||||
|
print("LIFE CYCLE STATE: $state");
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -68,7 +86,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
job: successJob,
|
job: successJob,
|
||||||
externalData: null,
|
externalData: null,
|
||||||
builder: (context, query) {
|
builder: (context, query) {
|
||||||
if (query.isLoading || query.isRefetching) {
|
if (!query.hasData || query.isLoading || query.isRefetching) {
|
||||||
return const CircularProgressIndicator();
|
return const CircularProgressIndicator();
|
||||||
}
|
}
|
||||||
return TextButton(
|
return TextButton(
|
||||||
@@ -83,7 +101,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
job: successJob,
|
job: successJob,
|
||||||
externalData: null,
|
externalData: null,
|
||||||
builder: (context, query) {
|
builder: (context, query) {
|
||||||
if (query.isLoading || query.isRefetching) {
|
if (!query.hasData || query.isLoading || query.isRefetching) {
|
||||||
return const CircularProgressIndicator();
|
return const CircularProgressIndicator();
|
||||||
}
|
}
|
||||||
return ElevatedButton(
|
return ElevatedButton(
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:fl_query/models/mutation_job.dart';
|
import 'package:fl_query/fl_query.dart';
|
||||||
import 'package:fl_query/mutation_builder.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class QueryWithExternalData extends StatelessWidget {
|
|||||||
job: jobWithExternalData,
|
job: jobWithExternalData,
|
||||||
externalData: (Random().nextDouble() * 200).toString(),
|
externalData: (Random().nextDouble() * 200).toString(),
|
||||||
builder: (context, query) {
|
builder: (context, query) {
|
||||||
if (query.isLoading || query.isLoading || query.data == null) {
|
if (query.isLoading || query.isRefetching || !query.hasData) {
|
||||||
return const CircularProgressIndicator();
|
return const CircularProgressIndicator();
|
||||||
}
|
}
|
||||||
return Container(
|
return Container(
|
||||||
@@ -32,6 +32,7 @@ class QueryWithExternalData extends StatelessWidget {
|
|||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
),
|
),
|
||||||
|
child: Text(query.externalData),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
abstract class BaseOperation<Data, StatusType> extends ChangeNotifier {
|
||||||
|
/// The number of times the query should refetch in the time of error
|
||||||
|
/// before giving up
|
||||||
|
final int retries;
|
||||||
|
final Duration retryDelay;
|
||||||
|
|
||||||
|
// got from global options
|
||||||
|
@protected
|
||||||
|
Duration cacheTime;
|
||||||
|
|
||||||
|
// all properties
|
||||||
|
Data? data;
|
||||||
|
dynamic error;
|
||||||
|
StatusType status;
|
||||||
|
|
||||||
|
/// total count of how many times the query retried to get a successful
|
||||||
|
/// result
|
||||||
|
int retryAttempts = 0;
|
||||||
|
DateTime updatedAt;
|
||||||
|
|
||||||
|
@protected
|
||||||
|
bool fetched = false;
|
||||||
|
|
||||||
|
/// used for keeping track of query activity. If the are no mounts &
|
||||||
|
/// the passed cached time is over than the query is removed from
|
||||||
|
/// storage/cache
|
||||||
|
Set<ValueKey<String>> _mounts = {};
|
||||||
|
|
||||||
|
BaseOperation({
|
||||||
|
required this.cacheTime,
|
||||||
|
required this.retries,
|
||||||
|
required this.retryDelay,
|
||||||
|
required this.status,
|
||||||
|
this.data,
|
||||||
|
}) : updatedAt = DateTime.now();
|
||||||
|
|
||||||
|
void mount(ValueKey<String> uKey) {
|
||||||
|
_mounts.add(uKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
void unmount(ValueKey<String> uKey) {
|
||||||
|
if (_mounts.length == 1) {
|
||||||
|
Future.delayed(cacheTime, () {
|
||||||
|
_mounts.remove(uKey);
|
||||||
|
// for letting know QueryBowl that this one's time has come for
|
||||||
|
// getting crushed
|
||||||
|
notifyListeners();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
_mounts.remove(uKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<ValueKey<String>> get mounts => _mounts;
|
||||||
|
|
||||||
|
bool get isSuccess;
|
||||||
|
bool get isError;
|
||||||
|
bool get isLoading;
|
||||||
|
bool get isIdle;
|
||||||
|
bool get isInactive => mounts.isEmpty;
|
||||||
|
bool get hasData => isSuccess && data != null;
|
||||||
|
bool get hasError => isError && error != null;
|
||||||
|
}
|
||||||
@@ -3,4 +3,7 @@ library fl_query;
|
|||||||
export 'query.dart';
|
export 'query.dart';
|
||||||
export 'query_bowl.dart';
|
export 'query_bowl.dart';
|
||||||
export 'query_builder.dart';
|
export 'query_builder.dart';
|
||||||
|
export 'mutation.dart';
|
||||||
|
export 'mutation_builder.dart';
|
||||||
export 'models/query_job.dart';
|
export 'models/query_job.dart';
|
||||||
|
export 'models/mutation_job.dart';
|
||||||
|
|||||||
@@ -1,17 +1,43 @@
|
|||||||
import 'package:fl_query/mutation.dart';
|
import 'package:fl_query/mutation.dart';
|
||||||
|
|
||||||
class MutationJob<T extends Object, V> {
|
class MutationJob<T extends Object, V> {
|
||||||
final String mutationKey;
|
String _mutationKey;
|
||||||
MutationTaskFunction<T, V> task;
|
MutationTaskFunction<T, V> task;
|
||||||
final int? retries;
|
final int? retries;
|
||||||
final Duration? retryDelay;
|
final Duration? retryDelay;
|
||||||
final Duration? cacheTime;
|
final Duration? cacheTime;
|
||||||
|
|
||||||
MutationJob({
|
MutationJob({
|
||||||
required this.mutationKey,
|
required String mutationKey,
|
||||||
required this.task,
|
required this.task,
|
||||||
this.retries,
|
this.retries,
|
||||||
this.retryDelay,
|
this.retryDelay,
|
||||||
this.cacheTime,
|
this.cacheTime,
|
||||||
});
|
}) : _mutationKey = mutationKey;
|
||||||
|
|
||||||
|
String get mutationKey => _mutationKey;
|
||||||
|
|
||||||
|
static MutationJob<T, V> Function(String queryKey)
|
||||||
|
withVariableKey<T extends Object, V>({
|
||||||
|
required MutationTaskFunction<T, V> task,
|
||||||
|
|
||||||
|
/// a extra key joined with mutationKey by a '#'
|
||||||
|
///
|
||||||
|
/// useful for matching a group mutation
|
||||||
|
String? preMutationKey,
|
||||||
|
int? retries,
|
||||||
|
Duration? retryDelay,
|
||||||
|
Duration? cacheTime,
|
||||||
|
}) {
|
||||||
|
return (String mutationKey) {
|
||||||
|
if (preMutationKey != null) mutationKey = "$preMutationKey#$mutationKey";
|
||||||
|
return MutationJob<T, V>(
|
||||||
|
mutationKey: mutationKey,
|
||||||
|
task: task,
|
||||||
|
retries: retries,
|
||||||
|
retryDelay: retryDelay,
|
||||||
|
cacheTime: cacheTime,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import 'package:fl_query/query.dart';
|
|||||||
|
|
||||||
class QueryJob<T extends Object, Outside> {
|
class QueryJob<T extends Object, Outside> {
|
||||||
// all params
|
// all params
|
||||||
final String queryKey;
|
String _queryKey;
|
||||||
QueryTaskFunction<T, Outside> task;
|
QueryTaskFunction<T, Outside> task;
|
||||||
final int? retries;
|
final int? retries;
|
||||||
final Duration? retryDelay;
|
final Duration? retryDelay;
|
||||||
@@ -13,11 +13,14 @@ class QueryJob<T extends Object, Outside> {
|
|||||||
final bool? enabled;
|
final bool? enabled;
|
||||||
|
|
||||||
// got from global options
|
// got from global options
|
||||||
final Duration? staleTime;
|
bool? refetchOnMount;
|
||||||
final Duration? cacheTime;
|
Duration? staleTime;
|
||||||
|
Duration? cacheTime;
|
||||||
|
|
||||||
|
Duration? refetchInterval;
|
||||||
|
|
||||||
QueryJob({
|
QueryJob({
|
||||||
required this.queryKey,
|
required String queryKey,
|
||||||
required this.task,
|
required this.task,
|
||||||
this.retries,
|
this.retries,
|
||||||
this.retryDelay,
|
this.retryDelay,
|
||||||
@@ -25,5 +28,43 @@ class QueryJob<T extends Object, Outside> {
|
|||||||
this.staleTime,
|
this.staleTime,
|
||||||
this.cacheTime,
|
this.cacheTime,
|
||||||
this.enabled,
|
this.enabled,
|
||||||
});
|
this.refetchInterval,
|
||||||
|
this.refetchOnMount,
|
||||||
|
}) : _queryKey = queryKey;
|
||||||
|
|
||||||
|
String get queryKey => _queryKey;
|
||||||
|
|
||||||
|
static QueryJob<T, Outside> Function(String queryKey)
|
||||||
|
withVariableKey<T extends Object, Outside>({
|
||||||
|
required QueryTaskFunction<T, Outside> task,
|
||||||
|
|
||||||
|
/// a extra key joined with queryKey by a '#'
|
||||||
|
///
|
||||||
|
/// useful for matching a group query
|
||||||
|
String? preQueryKey,
|
||||||
|
int? retries,
|
||||||
|
Duration? retryDelay,
|
||||||
|
T? initialData,
|
||||||
|
Duration? staleTime,
|
||||||
|
Duration? cacheTime,
|
||||||
|
bool? enabled,
|
||||||
|
Duration? refetchInterval,
|
||||||
|
bool? refetchOnMount,
|
||||||
|
}) {
|
||||||
|
return (String queryKey) {
|
||||||
|
if (preQueryKey != null) queryKey = "$preQueryKey#$queryKey";
|
||||||
|
return QueryJob<T, Outside>(
|
||||||
|
queryKey: queryKey,
|
||||||
|
task: task,
|
||||||
|
retries: retries,
|
||||||
|
retryDelay: retryDelay,
|
||||||
|
initialData: initialData,
|
||||||
|
staleTime: staleTime,
|
||||||
|
cacheTime: cacheTime,
|
||||||
|
enabled: enabled,
|
||||||
|
refetchInterval: refetchInterval,
|
||||||
|
refetchOnMount: refetchOnMount,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,24 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:fl_query/base_operation.dart';
|
||||||
import 'package:fl_query/models/mutation_job.dart';
|
import 'package:fl_query/models/mutation_job.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
enum MutationStatus {
|
enum MutationStatus {
|
||||||
failed,
|
error,
|
||||||
succeed,
|
success,
|
||||||
pending,
|
loading,
|
||||||
|
idle,
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef MutationListener<T> = FutureOr<void> Function(T);
|
typedef MutationListener<T> = FutureOr<void> Function(T);
|
||||||
|
|
||||||
typedef MutationTaskFunction<T, V> = FutureOr<T> Function(String, V);
|
typedef MutationTaskFunction<T, V> = FutureOr<T> Function(String, V);
|
||||||
|
|
||||||
class Mutation<T extends Object, V> extends ChangeNotifier {
|
class Mutation<T extends Object, V> extends BaseOperation<T, MutationStatus> {
|
||||||
// all params
|
// all params
|
||||||
final String mutationKey;
|
final String mutationKey;
|
||||||
MutationTaskFunction<T, V> task;
|
MutationTaskFunction<T, V> task;
|
||||||
final int retries;
|
|
||||||
final Duration retryDelay;
|
|
||||||
final Duration _cacheTime;
|
|
||||||
|
|
||||||
// all properties
|
|
||||||
T? data;
|
|
||||||
dynamic error;
|
|
||||||
MutationStatus status;
|
|
||||||
|
|
||||||
/// total count of how many times the query retried to get a successful
|
|
||||||
/// result
|
|
||||||
int retryAttempts = 0;
|
|
||||||
DateTime updatedAt;
|
|
||||||
|
|
||||||
/// used for keeping track of mutation activity. If the are no mounts &
|
|
||||||
/// the passed cached time is over than the mutation is removed from
|
|
||||||
/// storage/cache
|
|
||||||
Set<ValueKey<String>> _mounts = {};
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
final Set<MutationListener<T>> onDataListeners = {};
|
final Set<MutationListener<T>> onDataListeners = {};
|
||||||
@@ -46,15 +30,13 @@ class Mutation<T extends Object, V> extends ChangeNotifier {
|
|||||||
Mutation({
|
Mutation({
|
||||||
required this.mutationKey,
|
required this.mutationKey,
|
||||||
required this.task,
|
required this.task,
|
||||||
required this.retries,
|
required super.retries,
|
||||||
required this.retryDelay,
|
required super.retryDelay,
|
||||||
required Duration cacheTime,
|
required Duration cacheTime,
|
||||||
MutationListener<T>? onData,
|
MutationListener<T>? onData,
|
||||||
MutationListener<dynamic>? onError,
|
MutationListener<dynamic>? onError,
|
||||||
MutationListener<V>? onMutate,
|
MutationListener<V>? onMutate,
|
||||||
}) : status = MutationStatus.pending,
|
}) : super(cacheTime: cacheTime, status: MutationStatus.idle) {
|
||||||
updatedAt = DateTime.now(),
|
|
||||||
_cacheTime = cacheTime {
|
|
||||||
if (onData != null) onDataListeners.add(onData);
|
if (onData != null) onDataListeners.add(onData);
|
||||||
if (onError != null) onErrorListeners.add(onError);
|
if (onError != null) onErrorListeners.add(onError);
|
||||||
if (onMutate != null) onMutateListeners.add(onMutate);
|
if (onMutate != null) onMutateListeners.add(onMutate);
|
||||||
@@ -67,61 +49,38 @@ class Mutation<T extends Object, V> extends ChangeNotifier {
|
|||||||
MutationListener<V>? onMutate,
|
MutationListener<V>? onMutate,
|
||||||
}) : mutationKey = options.mutationKey,
|
}) : mutationKey = options.mutationKey,
|
||||||
task = options.task,
|
task = options.task,
|
||||||
retries = options.retries ?? 3,
|
super(
|
||||||
retryDelay = options.retryDelay ?? const Duration(milliseconds: 200),
|
retries: options.retries ?? 3,
|
||||||
_cacheTime = options.cacheTime ?? const Duration(minutes: 5),
|
retryDelay: options.retryDelay ?? const Duration(milliseconds: 200),
|
||||||
status = MutationStatus.pending,
|
cacheTime: options.cacheTime ?? const Duration(minutes: 5),
|
||||||
updatedAt = DateTime.now() {
|
status: MutationStatus.idle,
|
||||||
|
) {
|
||||||
if (onData != null) onDataListeners.add(onData);
|
if (onData != null) onDataListeners.add(onData);
|
||||||
if (onError != null) onErrorListeners.add(onError);
|
if (onError != null) onErrorListeners.add(onError);
|
||||||
}
|
}
|
||||||
|
|
||||||
// all getters & setters
|
|
||||||
bool get hasData => data != null && error == null;
|
|
||||||
bool get hasError =>
|
|
||||||
status == MutationStatus.failed && error != null && data == null;
|
|
||||||
bool get isLoading =>
|
|
||||||
status == MutationStatus.pending && data == null && error == null;
|
|
||||||
bool get isSucceeded => status == MutationStatus.succeed && data != null;
|
|
||||||
bool get isIdle => isSucceeded && error == null;
|
|
||||||
bool get isInactive => _mounts.isEmpty;
|
|
||||||
// all methods
|
// all methods
|
||||||
|
|
||||||
void mount(ValueKey<String> uKey) {
|
|
||||||
_mounts.add(uKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
void unmount(ValueKey<String> uKey) {
|
|
||||||
if (_mounts.length == 1) {
|
|
||||||
Future.delayed(_cacheTime, () {
|
|
||||||
_mounts.remove(uKey);
|
|
||||||
// for letting know QueryBowl that this one's time has come for
|
|
||||||
// getting crushed
|
|
||||||
notifyListeners();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
_mounts.remove(uKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calls the task function & doesn't check if there's already
|
/// Calls the task function & doesn't check if there's already
|
||||||
/// cached data available
|
/// cached data available
|
||||||
Future<void> _execMutation(V variables) async {
|
Future<void> _execute(V variables) async {
|
||||||
try {
|
try {
|
||||||
|
status = MutationStatus.loading;
|
||||||
|
notifyListeners();
|
||||||
retryAttempts = 0;
|
retryAttempts = 0;
|
||||||
for (final onMutate in onMutateListeners) {
|
for (final onMutate in onMutateListeners) {
|
||||||
onMutate(variables);
|
onMutate(variables);
|
||||||
}
|
}
|
||||||
data = await task(mutationKey, variables);
|
data = await task(mutationKey, variables);
|
||||||
updatedAt = DateTime.now();
|
updatedAt = DateTime.now();
|
||||||
status = MutationStatus.succeed;
|
status = MutationStatus.success;
|
||||||
for (final onData in onDataListeners) {
|
for (final onData in onDataListeners) {
|
||||||
onData(data!);
|
onData(data!);
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (retries == 0) {
|
if (retries == 0) {
|
||||||
status = MutationStatus.failed;
|
status = MutationStatus.error;
|
||||||
error = e;
|
error = e;
|
||||||
for (final onError in onErrorListeners) {
|
for (final onError in onErrorListeners) {
|
||||||
onError(error);
|
onError(error);
|
||||||
@@ -136,7 +95,7 @@ class Mutation<T extends Object, V> extends ChangeNotifier {
|
|||||||
onMutate(variables);
|
onMutate(variables);
|
||||||
}
|
}
|
||||||
data = await task(mutationKey, variables);
|
data = await task(mutationKey, variables);
|
||||||
status = MutationStatus.succeed;
|
status = MutationStatus.success;
|
||||||
for (final onData in onDataListeners) {
|
for (final onData in onDataListeners) {
|
||||||
onData(data!);
|
onData(data!);
|
||||||
}
|
}
|
||||||
@@ -144,7 +103,7 @@ class Mutation<T extends Object, V> extends ChangeNotifier {
|
|||||||
break;
|
break;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (retryAttempts == retries) {
|
if (retryAttempts == retries) {
|
||||||
status = MutationStatus.failed;
|
status = MutationStatus.error;
|
||||||
error = e;
|
error = e;
|
||||||
for (final onError in onErrorListeners) {
|
for (final onError in onErrorListeners) {
|
||||||
onError(error);
|
onError(error);
|
||||||
@@ -165,25 +124,45 @@ class Mutation<T extends Object, V> extends ChangeNotifier {
|
|||||||
}) {
|
}) {
|
||||||
if (onData != null) onDataListeners.add(onData);
|
if (onData != null) onDataListeners.add(onData);
|
||||||
if (onError != null) onErrorListeners.add(onError);
|
if (onError != null) onErrorListeners.add(onError);
|
||||||
_execMutation(variables).then((_) {
|
_execute(variables).then((_) {
|
||||||
onDataListeners.remove(onData);
|
onDataListeners.remove(onData);
|
||||||
onErrorListeners.remove(onError);
|
onErrorListeners.remove(onError);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<T?> mutateAsync(V variables) async {
|
Future<T?> mutateAsync(V variables) async {
|
||||||
return await _execMutation(variables).then((_) => data);
|
return await _execute(variables).then((_) => data);
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
/// Update configurations of the mutation after already creating the
|
||||||
|
/// Mutation instance
|
||||||
|
void updateDefaultOptions({
|
||||||
|
Duration? cacheTime,
|
||||||
|
}) {
|
||||||
|
if (this.cacheTime == Duration(minutes: 5) && cacheTime != null)
|
||||||
|
this.cacheTime = cacheTime;
|
||||||
|
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
data = null;
|
data = null;
|
||||||
retryAttempts = 0;
|
retryAttempts = 0;
|
||||||
updatedAt = DateTime.now();
|
updatedAt = DateTime.now();
|
||||||
onDataListeners.clear();
|
onDataListeners.clear();
|
||||||
onErrorListeners.clear();
|
onErrorListeners.clear();
|
||||||
status = MutationStatus.pending;
|
status = MutationStatus.idle;
|
||||||
onMutateListeners.clear();
|
onMutateListeners.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
A? cast<A>() => this is A ? this as A : null;
|
A? cast<A>() => this is A ? this as A : null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isError => status == MutationStatus.error;
|
||||||
|
@override
|
||||||
|
bool get isIdle => status == MutationStatus.idle;
|
||||||
|
@override
|
||||||
|
bool get isLoading => status == MutationStatus.loading;
|
||||||
|
@override
|
||||||
|
bool get isSuccess => status == MutationStatus.success;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,25 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:fl_query/base_operation.dart';
|
||||||
import 'package:fl_query/models/query_job.dart';
|
import 'package:fl_query/models/query_job.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
enum QueryStatus {
|
enum QueryStatus {
|
||||||
failed,
|
/// in times when an error occurs
|
||||||
succeed,
|
/// will get reset to idle on refetch/retry
|
||||||
pending,
|
error,
|
||||||
|
|
||||||
|
/// when a query successfully executes
|
||||||
|
success,
|
||||||
|
|
||||||
|
/// when the query is running (not refetching)
|
||||||
|
loading,
|
||||||
|
|
||||||
|
/// when the query isn't yet fetched, re-fetched, or got reset
|
||||||
|
/// mostly when both [data] & [error] are null. Also [fetched] is false
|
||||||
|
idle,
|
||||||
|
|
||||||
|
/// when the query is refetching (rerunning)
|
||||||
refetching;
|
refetching;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,36 +31,23 @@ typedef ListenerUnsubscriber = void Function();
|
|||||||
|
|
||||||
typedef QueryUpdateFunction<T> = FutureOr<T> Function(T? oldData);
|
typedef QueryUpdateFunction<T> = FutureOr<T> Function(T? oldData);
|
||||||
|
|
||||||
class Query<T extends Object, Outside> extends ChangeNotifier {
|
class Query<T extends Object, Outside> extends BaseOperation<T, QueryStatus> {
|
||||||
// all params
|
// all params
|
||||||
final String queryKey;
|
final String queryKey;
|
||||||
QueryTaskFunction<T, Outside> task;
|
QueryTaskFunction<T, Outside> task;
|
||||||
|
|
||||||
/// The number of times the query should refetch in the time of error
|
bool? refetchOnMount;
|
||||||
/// before giving up
|
|
||||||
final int retries;
|
|
||||||
final Duration retryDelay;
|
|
||||||
final T? _initialData;
|
final T? _initialData;
|
||||||
|
|
||||||
// got from global options
|
// got from global options
|
||||||
final Duration _staleTime;
|
Duration _staleTime;
|
||||||
final Duration _cacheTime;
|
|
||||||
|
|
||||||
// all properties
|
|
||||||
T? data;
|
|
||||||
dynamic error;
|
|
||||||
QueryStatus status;
|
|
||||||
|
|
||||||
/// total count of how many times the query retried to get a successful
|
/// total count of how many times the query retried to get a successful
|
||||||
/// result
|
/// result
|
||||||
int retryAttempts = 0;
|
|
||||||
DateTime updatedAt;
|
|
||||||
int refetchCount = 0;
|
int refetchCount = 0;
|
||||||
bool enabled;
|
bool enabled;
|
||||||
|
|
||||||
@protected
|
|
||||||
bool fetched = false;
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
final Set<QueryListener<T>> onDataListeners = Set<QueryListener<T>>();
|
final Set<QueryListener<T>> onDataListeners = Set<QueryListener<T>>();
|
||||||
@protected
|
@protected
|
||||||
@@ -60,32 +60,37 @@ class Query<T extends Object, Outside> extends ChangeNotifier {
|
|||||||
|
|
||||||
Outside? _prevUsedExternalData;
|
Outside? _prevUsedExternalData;
|
||||||
|
|
||||||
/// used for keeping track of query activity. If the are no mounts &
|
Duration? refetchInterval;
|
||||||
/// the passed cached time is over than the query is removed from
|
|
||||||
/// storage/cache
|
Timer? _refetchIntervalTimer;
|
||||||
Set<ValueKey<String>> _mounts = {};
|
|
||||||
|
|
||||||
Query({
|
Query({
|
||||||
required this.queryKey,
|
required this.queryKey,
|
||||||
required this.task,
|
required this.task,
|
||||||
required Duration staleTime,
|
required Duration staleTime,
|
||||||
required Duration cacheTime,
|
required super.cacheTime,
|
||||||
required Outside externalData,
|
required Outside externalData,
|
||||||
required this.retries,
|
required super.retries,
|
||||||
required this.retryDelay,
|
required super.retryDelay,
|
||||||
T? initialData,
|
this.refetchOnMount,
|
||||||
|
this.refetchInterval,
|
||||||
this.enabled = true,
|
this.enabled = true,
|
||||||
|
T? initialData,
|
||||||
QueryListener<T>? onData,
|
QueryListener<T>? onData,
|
||||||
QueryListener<dynamic>? onError,
|
QueryListener<dynamic>? onError,
|
||||||
}) : status = QueryStatus.pending,
|
}) : _staleTime = staleTime,
|
||||||
_staleTime = staleTime,
|
|
||||||
_cacheTime = cacheTime,
|
|
||||||
_initialData = initialData,
|
_initialData = initialData,
|
||||||
_externalData = externalData,
|
_externalData = externalData,
|
||||||
data = initialData,
|
super(
|
||||||
updatedAt = DateTime.now() {
|
status: QueryStatus.idle,
|
||||||
|
data: initialData,
|
||||||
|
) {
|
||||||
if (onData != null) onDataListeners.add(onData);
|
if (onData != null) onDataListeners.add(onData);
|
||||||
if (onError != null) onErrorListeners.add(onError);
|
if (onError != null) onErrorListeners.add(onError);
|
||||||
|
|
||||||
|
if (refetchInterval != null && refetchInterval != Duration.zero) {
|
||||||
|
_refetchIntervalTimer = _createRefetchTimer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Query.fromOptions(
|
Query.fromOptions(
|
||||||
@@ -96,50 +101,34 @@ class Query<T extends Object, Outside> extends ChangeNotifier {
|
|||||||
}) : queryKey = options.queryKey,
|
}) : queryKey = options.queryKey,
|
||||||
enabled = options.enabled ?? true,
|
enabled = options.enabled ?? true,
|
||||||
task = options.task,
|
task = options.task,
|
||||||
retries = options.retries ?? 3,
|
|
||||||
retryDelay = options.retryDelay ?? const Duration(milliseconds: 200),
|
|
||||||
_staleTime = options.staleTime ?? const Duration(milliseconds: 500),
|
_staleTime = options.staleTime ?? const Duration(milliseconds: 500),
|
||||||
_cacheTime = options.cacheTime ?? const Duration(minutes: 5),
|
|
||||||
_initialData = options.initialData,
|
_initialData = options.initialData,
|
||||||
_externalData = externalData,
|
_externalData = externalData,
|
||||||
data = options.initialData,
|
refetchInterval = options.refetchInterval,
|
||||||
status = QueryStatus.pending,
|
refetchOnMount = options.refetchOnMount,
|
||||||
updatedAt = DateTime.now() {
|
super(
|
||||||
|
status: QueryStatus.idle,
|
||||||
|
cacheTime: options.cacheTime ?? const Duration(minutes: 5),
|
||||||
|
retries: options.retries ?? 3,
|
||||||
|
retryDelay: options.retryDelay ?? const Duration(milliseconds: 200),
|
||||||
|
data: options.initialData,
|
||||||
|
) {
|
||||||
if (onData != null) onDataListeners.add(onData);
|
if (onData != null) onDataListeners.add(onData);
|
||||||
if (onError != null) onErrorListeners.add(onError);
|
if (onError != null) onErrorListeners.add(onError);
|
||||||
}
|
}
|
||||||
|
|
||||||
// all getters & setters
|
// all getters & setters
|
||||||
bool get hasData => data != null && error == null;
|
|
||||||
bool get hasError =>
|
|
||||||
status == QueryStatus.failed && error != null && data == null;
|
|
||||||
bool get isLoading =>
|
|
||||||
status == QueryStatus.pending && data == null && error == null;
|
|
||||||
bool get isRefetching =>
|
|
||||||
status == QueryStatus.refetching && (data != null || error != null);
|
|
||||||
bool get isSucceeded => status == QueryStatus.succeed && data != null;
|
|
||||||
bool get isIdle => isSucceeded && error == null;
|
|
||||||
bool get isInactive => _mounts.isEmpty;
|
|
||||||
Outside get externalData => _externalData;
|
Outside get externalData => _externalData;
|
||||||
Outside? get prevUsedExternalData => _prevUsedExternalData;
|
Outside? get prevUsedExternalData => _prevUsedExternalData;
|
||||||
|
|
||||||
// all methods
|
Timer _createRefetchTimer() {
|
||||||
|
return Timer.periodic(
|
||||||
void mount(ValueKey<String> uKey) {
|
refetchInterval!,
|
||||||
_mounts.add(uKey);
|
(_) async {
|
||||||
}
|
if (isStale) await refetch();
|
||||||
|
},
|
||||||
void unmount(ValueKey<String> uKey) {
|
);
|
||||||
if (_mounts.length == 1) {
|
|
||||||
Future.delayed(_cacheTime, () {
|
|
||||||
_mounts.remove(uKey);
|
|
||||||
// for letting know QueryBowl that this one's time has come for
|
|
||||||
// getting crushed
|
|
||||||
notifyListeners();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
_mounts.remove(uKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calls the task function & doesn't check if there's already
|
/// Calls the task function & doesn't check if there's already
|
||||||
@@ -150,14 +139,14 @@ class Query<T extends Object, Outside> extends ChangeNotifier {
|
|||||||
data = await task(queryKey, _externalData);
|
data = await task(queryKey, _externalData);
|
||||||
_prevUsedExternalData = _externalData;
|
_prevUsedExternalData = _externalData;
|
||||||
updatedAt = DateTime.now();
|
updatedAt = DateTime.now();
|
||||||
status = QueryStatus.succeed;
|
status = QueryStatus.success;
|
||||||
for (final onData in onDataListeners) {
|
for (final onData in onDataListeners) {
|
||||||
onData(data!);
|
onData(data!);
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (retries == 0) {
|
if (retries == 0) {
|
||||||
status = QueryStatus.failed;
|
status = QueryStatus.error;
|
||||||
error = e;
|
error = e;
|
||||||
for (final onError in onErrorListeners) {
|
for (final onError in onErrorListeners) {
|
||||||
onError(error);
|
onError(error);
|
||||||
@@ -170,18 +159,18 @@ class Query<T extends Object, Outside> extends ChangeNotifier {
|
|||||||
try {
|
try {
|
||||||
data = await task(queryKey, _externalData);
|
data = await task(queryKey, _externalData);
|
||||||
_prevUsedExternalData = _externalData;
|
_prevUsedExternalData = _externalData;
|
||||||
status = QueryStatus.succeed;
|
status = QueryStatus.success;
|
||||||
for (final onData in onDataListeners) {
|
for (final onData in onDataListeners) {
|
||||||
onData(data!);
|
await onData(data!);
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
break;
|
break;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (retryAttempts == retries) {
|
if (retryAttempts == retries) {
|
||||||
status = QueryStatus.failed;
|
status = QueryStatus.error;
|
||||||
error = e;
|
error = e;
|
||||||
for (final onError in onErrorListeners) {
|
for (final onError in onErrorListeners) {
|
||||||
onError(error);
|
await onError(error);
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
@@ -193,12 +182,12 @@ class Query<T extends Object, Outside> extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<T?> fetch() async {
|
Future<T?> fetch() async {
|
||||||
status = QueryStatus.pending;
|
|
||||||
notifyListeners();
|
|
||||||
if (!enabled) return null;
|
if (!enabled) return null;
|
||||||
if (!isStale && hasData) {
|
if (hasData) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
status = QueryStatus.loading;
|
||||||
|
notifyListeners();
|
||||||
return _execute().then((_) {
|
return _execute().then((_) {
|
||||||
fetched = true;
|
fetched = true;
|
||||||
return data;
|
return data;
|
||||||
@@ -234,11 +223,12 @@ class Query<T extends Object, Outside> extends ChangeNotifier {
|
|||||||
"[fl_query] new instance of data should be returned because of immutability");
|
"[fl_query] new instance of data should be returned because of immutability");
|
||||||
}
|
}
|
||||||
data = newData;
|
data = newData;
|
||||||
status = QueryStatus.succeed;
|
status = QueryStatus.success;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
setExternalData(Outside externalData) {
|
setExternalData(Outside externalData) {
|
||||||
|
_prevUsedExternalData = _externalData;
|
||||||
_externalData = externalData;
|
_externalData = externalData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,19 +237,58 @@ class Query<T extends Object, Outside> extends ChangeNotifier {
|
|||||||
data = _initialData;
|
data = _initialData;
|
||||||
error = null;
|
error = null;
|
||||||
fetched = false;
|
fetched = false;
|
||||||
status = QueryStatus.pending;
|
status = QueryStatus.idle;
|
||||||
retryAttempts = 0;
|
retryAttempts = 0;
|
||||||
onDataListeners.clear();
|
onDataListeners.clear();
|
||||||
onErrorListeners.clear();
|
onErrorListeners.clear();
|
||||||
_mounts.clear();
|
mounts.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update configurations of the query after already creating the Query
|
||||||
|
/// instance
|
||||||
|
void updateDefaultOptions({
|
||||||
|
Duration? refetchInterval,
|
||||||
|
Duration? staleTime,
|
||||||
|
Duration? cacheTime,
|
||||||
|
bool? refetchOnMount,
|
||||||
|
}) {
|
||||||
|
if (this.refetchInterval == null &&
|
||||||
|
refetchInterval != null &&
|
||||||
|
refetchInterval != Duration.zero) {
|
||||||
|
this.refetchInterval = refetchInterval;
|
||||||
|
_refetchIntervalTimer?.cancel();
|
||||||
|
_refetchIntervalTimer = _createRefetchTimer();
|
||||||
|
}
|
||||||
|
if (this.cacheTime == Duration(minutes: 5) && cacheTime != null)
|
||||||
|
this.cacheTime = cacheTime;
|
||||||
|
if (this._staleTime == const Duration(milliseconds: 500) &&
|
||||||
|
staleTime != null) this._staleTime = staleTime;
|
||||||
|
if (this.refetchOnMount == null && refetchOnMount != null)
|
||||||
|
this.refetchOnMount = refetchOnMount;
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get isStale {
|
bool get isStale {
|
||||||
|
/// when [_staleTime] is [Duration.zero], the query will always be
|
||||||
|
/// stale & will never refetch in the background. But can be inactive
|
||||||
|
/// if [mounts.length] become zero
|
||||||
|
if (_staleTime == Duration.zero) return false;
|
||||||
|
|
||||||
// when current DateTime is after [update_at + stale_time] it means
|
// when current DateTime is after [update_at + stale_time] it means
|
||||||
// the data has become stale
|
// the data has become stale
|
||||||
return DateTime.now().isAfter(updatedAt.add(_staleTime));
|
return DateTime.now().isAfter(updatedAt.add(_staleTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isError => status == QueryStatus.error;
|
||||||
|
@override
|
||||||
|
bool get isIdle => status == QueryStatus.idle;
|
||||||
|
@override
|
||||||
|
bool get isLoading => status == QueryStatus.loading;
|
||||||
|
bool get isRefetching => status == QueryStatus.refetching;
|
||||||
|
@override
|
||||||
|
bool get isSuccess => status == QueryStatus.success;
|
||||||
|
|
||||||
A? cast<A>() => this is A ? this as A : null;
|
A? cast<A>() => this is A ? this as A : null;
|
||||||
|
|
||||||
String get debugLabel => "Query($queryKey)";
|
String get debugLabel => "Query($queryKey)";
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:fl_query/models/query_job.dart';
|
|||||||
import 'package:fl_query/mutation.dart';
|
import 'package:fl_query/mutation.dart';
|
||||||
import 'package:fl_query/query.dart';
|
import 'package:fl_query/query.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:fl_query/utils.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
class QueryBowlScope extends StatefulWidget {
|
class QueryBowlScope extends StatefulWidget {
|
||||||
@@ -11,14 +12,30 @@ class QueryBowlScope extends StatefulWidget {
|
|||||||
final Duration staleTime;
|
final Duration staleTime;
|
||||||
final Duration cacheTime;
|
final Duration cacheTime;
|
||||||
|
|
||||||
|
// refetching options
|
||||||
|
|
||||||
|
// refetch query when new query instance mounts
|
||||||
|
final bool refetchOnMount;
|
||||||
|
// for desktop & web only
|
||||||
|
final bool refetchOnWindowFocus;
|
||||||
|
// for mobile only
|
||||||
|
final bool refetchOnApplicationResume;
|
||||||
|
// refetch when user's device reconnects to the internet after no being
|
||||||
|
// connected before
|
||||||
|
final bool refetchOnReconnect;
|
||||||
|
|
||||||
/// used for periodically checking if any query got stale.
|
/// used for periodically checking if any query got stale.
|
||||||
/// If none is supplied then half of the value of staleTime is used
|
/// If none is supplied then half of the value of staleTime is used
|
||||||
final Duration refreshInterval;
|
final Duration refetchInterval;
|
||||||
const QueryBowlScope({
|
const QueryBowlScope({
|
||||||
required this.child,
|
required this.child,
|
||||||
this.staleTime = const Duration(milliseconds: 500),
|
this.staleTime = Duration.zero,
|
||||||
this.cacheTime = const Duration(minutes: 5),
|
this.cacheTime = const Duration(minutes: 5),
|
||||||
this.refreshInterval = const Duration(minutes: 5),
|
this.refetchInterval = Duration.zero,
|
||||||
|
this.refetchOnMount = false,
|
||||||
|
this.refetchOnReconnect = true,
|
||||||
|
this.refetchOnApplicationResume = true,
|
||||||
|
this.refetchOnWindowFocus = true,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@@ -30,34 +47,19 @@ class _QueryBowlScopeState extends State<QueryBowlScope> {
|
|||||||
late Set<Query> queries;
|
late Set<Query> queries;
|
||||||
late Set<Mutation> mutations;
|
late Set<Mutation> mutations;
|
||||||
|
|
||||||
late Timer refreshIntervalTimer;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
queries = {};
|
queries = {};
|
||||||
mutations = {};
|
mutations = {};
|
||||||
refreshIntervalTimer = Timer.periodic(
|
|
||||||
widget.refreshInterval,
|
|
||||||
_checkAndUpdateStaleQueriesOnBg,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
refreshIntervalTimer.cancel();
|
|
||||||
_disposeUpdateListeners();
|
_disposeUpdateListeners();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _checkAndUpdateStaleQueriesOnBg([dynamic _]) async {
|
|
||||||
// checking for staled queries inside the widget as InheritedWidget
|
|
||||||
// classes has to be constant & doesn't this kind of dynamic behavior
|
|
||||||
for (final query in queries) {
|
|
||||||
if (query.isStale) await query.refetch();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _listenToUpdates() {
|
void _listenToUpdates() {
|
||||||
for (final query in queries) {
|
for (final query in queries) {
|
||||||
query.addListener(() => updateQueries(query));
|
query.addListener(() => updateQueries(query));
|
||||||
@@ -147,6 +149,9 @@ class _QueryBowlScopeState extends State<QueryBowlScope> {
|
|||||||
queries: queries,
|
queries: queries,
|
||||||
mutations: mutations,
|
mutations: mutations,
|
||||||
staleTime: widget.staleTime,
|
staleTime: widget.staleTime,
|
||||||
|
cacheTime: widget.cacheTime,
|
||||||
|
refetchInterval: widget.refetchInterval,
|
||||||
|
refetchOnMount: widget.refetchOnMount,
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -158,6 +163,10 @@ class QueryBowl extends InheritedWidget {
|
|||||||
final Set<Query> _queries;
|
final Set<Query> _queries;
|
||||||
final Set<Mutation> _mutations;
|
final Set<Mutation> _mutations;
|
||||||
final Duration staleTime;
|
final Duration staleTime;
|
||||||
|
final Duration cacheTime;
|
||||||
|
|
||||||
|
final Duration? refetchInterval;
|
||||||
|
final bool refetchOnMount;
|
||||||
|
|
||||||
final void Function<T extends Object, Outside>(Query<T, Outside> query)
|
final void Function<T extends Object, Outside>(Query<T, Outside> query)
|
||||||
_addQuery;
|
_addQuery;
|
||||||
@@ -179,8 +188,11 @@ class QueryBowl extends InheritedWidget {
|
|||||||
required final Set<Query> queries,
|
required final Set<Query> queries,
|
||||||
required final Set<Mutation> mutations,
|
required final Set<Mutation> mutations,
|
||||||
required this.staleTime,
|
required this.staleTime,
|
||||||
|
required this.cacheTime,
|
||||||
required this.removeQueries,
|
required this.removeQueries,
|
||||||
required this.clear,
|
required this.clear,
|
||||||
|
required this.refetchOnMount,
|
||||||
|
this.refetchInterval,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : _addQuery = addQuery,
|
}) : _addQuery = addQuery,
|
||||||
_queries = queries,
|
_queries = queries,
|
||||||
@@ -201,8 +213,12 @@ class QueryBowl extends InheritedWidget {
|
|||||||
if (prevQuery is Query<T, Outside>) {
|
if (prevQuery is Query<T, Outside>) {
|
||||||
// run the query if its still not called or if externalData has
|
// run the query if its still not called or if externalData has
|
||||||
// changed
|
// changed
|
||||||
final hasExternalDataChanged =
|
final hasExternalDataChanged = prevQuery.prevUsedExternalData != null &&
|
||||||
prevQuery.prevUsedExternalData != externalData;
|
externalData != null &&
|
||||||
|
!isShallowEqual(
|
||||||
|
prevQuery.prevUsedExternalData!,
|
||||||
|
externalData,
|
||||||
|
);
|
||||||
prevQuery.mount(key);
|
prevQuery.mount(key);
|
||||||
if (onData != null) prevQuery.onDataListeners.add(onData);
|
if (onData != null) prevQuery.onDataListeners.add(onData);
|
||||||
if (onError != null) prevQuery.onErrorListeners.add(onError);
|
if (onError != null) prevQuery.onErrorListeners.add(onError);
|
||||||
@@ -215,6 +231,12 @@ class QueryBowl extends InheritedWidget {
|
|||||||
// mounting the widget that is using the query in the prevQuery
|
// mounting the widget that is using the query in the prevQuery
|
||||||
return prevQuery.data;
|
return prevQuery.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// populating with default configurations
|
||||||
|
options.refetchInterval ??= refetchInterval;
|
||||||
|
options.staleTime ??= staleTime;
|
||||||
|
options.cacheTime ??= cacheTime;
|
||||||
|
options.refetchOnMount ??= refetchOnMount;
|
||||||
final query = Query<T, Outside>.fromOptions(
|
final query = Query<T, Outside>.fromOptions(
|
||||||
options,
|
options,
|
||||||
externalData: externalData,
|
externalData: externalData,
|
||||||
@@ -238,8 +260,14 @@ class QueryBowl extends InheritedWidget {
|
|||||||
if (prevQuery is Query<T, Outside>) {
|
if (prevQuery is Query<T, Outside>) {
|
||||||
// run the query if its still not called or if externalData has
|
// run the query if its still not called or if externalData has
|
||||||
// changed
|
// changed
|
||||||
if (prevQuery.prevUsedExternalData != query.externalData)
|
if (prevQuery.prevUsedExternalData != null &&
|
||||||
|
query.externalData != null &&
|
||||||
|
!isShallowEqual(
|
||||||
|
prevQuery.prevUsedExternalData!,
|
||||||
|
query.externalData!,
|
||||||
|
)) {
|
||||||
prevQuery.setExternalData(query.externalData);
|
prevQuery.setExternalData(query.externalData);
|
||||||
|
}
|
||||||
prevQuery.mount(key);
|
prevQuery.mount(key);
|
||||||
if (onData != null) prevQuery.onDataListeners.add(onData);
|
if (onData != null) prevQuery.onDataListeners.add(onData);
|
||||||
if (onError != null) prevQuery.onErrorListeners.add(onError);
|
if (onError != null) prevQuery.onErrorListeners.add(onError);
|
||||||
@@ -248,6 +276,12 @@ class QueryBowl extends InheritedWidget {
|
|||||||
}
|
}
|
||||||
if (onData != null) query.onDataListeners.add(onData);
|
if (onData != null) query.onDataListeners.add(onData);
|
||||||
if (onError != null) query.onErrorListeners.add(onError);
|
if (onError != null) query.onErrorListeners.add(onError);
|
||||||
|
query.updateDefaultOptions(
|
||||||
|
cacheTime: cacheTime,
|
||||||
|
staleTime: staleTime,
|
||||||
|
refetchInterval: refetchInterval,
|
||||||
|
refetchOnMount: refetchOnMount,
|
||||||
|
);
|
||||||
query.mount(key);
|
query.mount(key);
|
||||||
_addQuery<T, Outside>(query);
|
_addQuery<T, Outside>(query);
|
||||||
return query;
|
return query;
|
||||||
@@ -270,6 +304,7 @@ class QueryBowl extends InheritedWidget {
|
|||||||
prevMutation.mount(key);
|
prevMutation.mount(key);
|
||||||
return prevMutation;
|
return prevMutation;
|
||||||
} else {
|
} else {
|
||||||
|
mutation.updateDefaultOptions(cacheTime: cacheTime);
|
||||||
mutation.mount(key);
|
mutation.mount(key);
|
||||||
_addMutation(mutation);
|
_addMutation(mutation);
|
||||||
return mutation;
|
return mutation;
|
||||||
|
|||||||
@@ -51,13 +51,20 @@ class _QueryBuilderState<T extends Object, Outside>
|
|||||||
onData: widget.onData,
|
onData: widget.onData,
|
||||||
onError: widget.onError,
|
onError: widget.onError,
|
||||||
);
|
);
|
||||||
await 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
|
@override
|
||||||
void didUpdateWidget(covariant oldWidget) {
|
void didUpdateWidget(covariant oldWidget) {
|
||||||
if (oldWidget.externalData != widget.externalData) {
|
if (oldWidget.externalData != null &&
|
||||||
|
widget.externalData != null &&
|
||||||
|
!isShallowEqual(oldWidget.externalData!, widget.externalData!)) {
|
||||||
QueryBowl.of(context).fetchQuery(
|
QueryBowl.of(context).fetchQuery(
|
||||||
widget.job,
|
widget.job,
|
||||||
externalData: widget.externalData,
|
externalData: widget.externalData,
|
||||||
|
|||||||
@@ -11,3 +11,33 @@ Future<void> callQueryListeners<T>(Set<QueryListener<T>> listeners, T data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const uuid = Uuid();
|
const uuid = Uuid();
|
||||||
|
|
||||||
|
bool isShallowEqualList(List list1, List list2) {
|
||||||
|
return list1.asMap().entries.every((l1Entry) {
|
||||||
|
return l1Entry.value == list2[l1Entry.key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isShallowEqualSet(Set list1, Set list2) {
|
||||||
|
return isShallowEqualList(list1.toList(), list2.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isShallowEqualMap(Map list1, Map list2) {
|
||||||
|
return list1.entries.every((l1Entry) {
|
||||||
|
return l1Entry.value == list2[l1Entry.key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isShallowEqual(Object obj1, Object obj2) {
|
||||||
|
if (obj1 is List && obj2 is List) {
|
||||||
|
return isShallowEqualList(obj1, obj2);
|
||||||
|
} else if (obj1 is Set && obj2 is Set) {
|
||||||
|
return isShallowEqualSet(obj1, obj2);
|
||||||
|
} else if (obj1 is Map && obj2 is Map) {
|
||||||
|
return isShallowEqualMap(obj1, obj2);
|
||||||
|
} else {
|
||||||
|
// for other types basically comparing references for non primitive
|
||||||
|
// types. And primitives are always compared by value
|
||||||
|
return obj1 == obj2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,12 +30,12 @@ void main() {
|
|||||||
expect(query.retryAttempts, 0);
|
expect(query.retryAttempts, 0);
|
||||||
expect(query.fetched, isFalse);
|
expect(query.fetched, isFalse);
|
||||||
expect(query.retryDelay, Duration(milliseconds: 200));
|
expect(query.retryDelay, Duration(milliseconds: 200));
|
||||||
expect(query.status, QueryStatus.pending);
|
expect(query.status, QueryStatus.loading);
|
||||||
expect(query.isStale, isFalse);
|
expect(query.isStale, isFalse);
|
||||||
expect(query.isIdle, isFalse);
|
expect(query.isIdle, isFalse);
|
||||||
expect(query.isInactive, isTrue);
|
expect(query.isInactive, isTrue);
|
||||||
expect(query.isLoading, isTrue);
|
expect(query.isLoading, isTrue);
|
||||||
expect(query.isSucceeded, isFalse);
|
expect(query.isSuccess, isFalse);
|
||||||
expect(query.hasError, isFalse);
|
expect(query.hasError, isFalse);
|
||||||
expect(query.hasData, isFalse);
|
expect(query.hasData, isFalse);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user