added lazy query support
fixed possible race condition of simultanous refetches of the same query
This commit is contained in:
@@ -0,0 +1,36 @@
|
|||||||
|
import 'package:fl_query/fl_query.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
final lazyQueryJob = QueryJob<String, String>(
|
||||||
|
queryKey: "non_enabled_query",
|
||||||
|
enabled: false,
|
||||||
|
task: (queryKey, data) {
|
||||||
|
return Future.delayed(const Duration(milliseconds: 500),
|
||||||
|
() => "Hello from $queryKey with $data");
|
||||||
|
});
|
||||||
|
|
||||||
|
class LazyQuery extends StatelessWidget {
|
||||||
|
const LazyQuery({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(),
|
||||||
|
body: QueryBuilder<String, String>(
|
||||||
|
job: lazyQueryJob,
|
||||||
|
externalData: "Love",
|
||||||
|
builder: (context, query) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Text("Query Data::: ${query.data ?? "Loading"}"),
|
||||||
|
ElevatedButton(
|
||||||
|
child: const Text("Fetch Query"),
|
||||||
|
onPressed: () => query.refetch(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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/lazy_query.dart';
|
||||||
import 'package:example/query_with_external_data.dart';
|
import 'package:example/query_with_external_data.dart';
|
||||||
import 'package:fl_query/fl_query.dart';
|
import 'package:fl_query/fl_query.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -35,7 +36,9 @@ final successJob = QueryJob<String, void>(
|
|||||||
|
|
||||||
final failedJob = QueryJob<String, void>(
|
final failedJob = QueryJob<String, void>(
|
||||||
queryKey: "failure",
|
queryKey: "failure",
|
||||||
task: (queryKey, _) => Future.value("[$queryKey] Failed for unknown reason"),
|
task: (queryKey, _) => Random().nextBool()
|
||||||
|
? Future.error("[$queryKey] Failed for unknown reason")
|
||||||
|
: Future.value("Success, you'll get slowly ${Random().nextInt(100)}!"),
|
||||||
);
|
);
|
||||||
|
|
||||||
class MyHomePage extends StatefulWidget {
|
class MyHomePage extends StatefulWidget {
|
||||||
@@ -90,8 +93,20 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
job: failedJob,
|
job: failedJob,
|
||||||
externalData: null,
|
externalData: null,
|
||||||
builder: (context, query) {
|
builder: (context, query) {
|
||||||
if (query.hasError) return Text(query.error);
|
return Row(
|
||||||
return Text("Failure. You're a failure ${query.data}");
|
children: [
|
||||||
|
if (query.hasError)
|
||||||
|
Text(
|
||||||
|
"${query.error}. Retrying: ${query.retryAttempts}"),
|
||||||
|
if (query.hasData)
|
||||||
|
Text(
|
||||||
|
"Success after ${query.retryAttempts}. Data: ${query.data}"),
|
||||||
|
ElevatedButton(
|
||||||
|
child: Text("Refetch ${query.queryKey}"),
|
||||||
|
onPressed: () => query.refetch(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -106,6 +121,17 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
ElevatedButton(
|
||||||
|
child: const Text("Non Enabled Query Example"),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const LazyQuery(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
const AnotherComponent(),
|
const AnotherComponent(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ class QueryJob<T extends Object, Outside> {
|
|||||||
final Duration? retryDelay;
|
final Duration? retryDelay;
|
||||||
final T? initialData;
|
final T? initialData;
|
||||||
|
|
||||||
|
/// If set to false then the initial fetch will not be called & to
|
||||||
|
/// start the process the user has to call the refetch first
|
||||||
|
final bool? enabled;
|
||||||
|
|
||||||
// got from global options
|
// got from global options
|
||||||
final Duration? staleTime;
|
final Duration? staleTime;
|
||||||
|
|
||||||
@@ -22,5 +26,6 @@ class QueryJob<T extends Object, Outside> {
|
|||||||
this.staleTime,
|
this.staleTime,
|
||||||
this.onData,
|
this.onData,
|
||||||
this.onError,
|
this.onError,
|
||||||
|
this.enabled,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ class Query<T extends Object, Outside> extends ChangeNotifier {
|
|||||||
// 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
|
||||||
|
/// before giving up
|
||||||
final int retries;
|
final int retries;
|
||||||
final Duration retryDelay;
|
final Duration retryDelay;
|
||||||
final T? _initialData;
|
final T? _initialData;
|
||||||
@@ -31,9 +34,13 @@ class Query<T extends Object, Outside> extends ChangeNotifier {
|
|||||||
T? data;
|
T? data;
|
||||||
dynamic error;
|
dynamic error;
|
||||||
QueryStatus status;
|
QueryStatus status;
|
||||||
|
|
||||||
|
/// total count of how many times the query retried to get a successful
|
||||||
|
/// result
|
||||||
int retryAttempts = 0;
|
int retryAttempts = 0;
|
||||||
DateTime updatedAt;
|
DateTime updatedAt;
|
||||||
int refetchCount = 0;
|
int refetchCount = 0;
|
||||||
|
bool enabled;
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
bool fetched = false;
|
bool fetched = false;
|
||||||
@@ -55,6 +62,7 @@ class Query<T extends Object, Outside> extends ChangeNotifier {
|
|||||||
required this.retries,
|
required this.retries,
|
||||||
required this.retryDelay,
|
required this.retryDelay,
|
||||||
T? initialData,
|
T? initialData,
|
||||||
|
this.enabled = true,
|
||||||
QueryListener<T>? onData,
|
QueryListener<T>? onData,
|
||||||
QueryListener<dynamic>? onError,
|
QueryListener<dynamic>? onError,
|
||||||
}) : status = QueryStatus.pending,
|
}) : status = QueryStatus.pending,
|
||||||
@@ -69,6 +77,7 @@ class Query<T extends Object, Outside> extends ChangeNotifier {
|
|||||||
Query.fromOptions(QueryJob<T, Outside> options,
|
Query.fromOptions(QueryJob<T, Outside> options,
|
||||||
{required Outside externalData})
|
{required Outside externalData})
|
||||||
: queryKey = options.queryKey,
|
: queryKey = options.queryKey,
|
||||||
|
enabled = options.enabled ?? true,
|
||||||
task = options.task,
|
task = options.task,
|
||||||
retries = options.retries ?? 3,
|
retries = options.retries ?? 3,
|
||||||
retryDelay = options.retryDelay ?? const Duration(milliseconds: 200),
|
retryDelay = options.retryDelay ?? const Duration(milliseconds: 200),
|
||||||
@@ -90,6 +99,7 @@ class Query<T extends Object, Outside> extends ChangeNotifier {
|
|||||||
bool get isRefetching =>
|
bool get isRefetching =>
|
||||||
status == QueryStatus.refetching && (data != null || error != null);
|
status == QueryStatus.refetching && (data != null || error != null);
|
||||||
bool get isSucceeded => status == QueryStatus.succeed && data != null;
|
bool get isSucceeded => status == QueryStatus.succeed && data != null;
|
||||||
|
bool get isIdle => isSucceeded && error == null;
|
||||||
Outside get externalData => _externalData;
|
Outside get externalData => _externalData;
|
||||||
Outside? get prevUsedExternalData => _prevUsedExternalData;
|
Outside? get prevUsedExternalData => _prevUsedExternalData;
|
||||||
|
|
||||||
@@ -140,6 +150,7 @@ class Query<T extends Object, Outside> extends ChangeNotifier {
|
|||||||
Future<T?> fetch() async {
|
Future<T?> fetch() async {
|
||||||
status = QueryStatus.pending;
|
status = QueryStatus.pending;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
if (!enabled) return null;
|
||||||
if (!isStale && hasData) {
|
if (!isStale && hasData) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@@ -149,11 +160,16 @@ class Query<T extends Object, Outside> extends ChangeNotifier {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<T?> refetch() {
|
Future<T?> refetch() async {
|
||||||
|
// cannot let run multiple refetch at the same time. It can cause
|
||||||
|
// race-condition
|
||||||
|
if (isRefetching) return null;
|
||||||
status = QueryStatus.refetching;
|
status = QueryStatus.refetching;
|
||||||
refetchCount++;
|
refetchCount++;
|
||||||
|
// disabling the lazy query bound when query was actually called
|
||||||
|
if (!enabled) enabled = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return _execute().then((_) => data);
|
return await _execute().then((_) => data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// can be used to update the data manually. Can be useful when used
|
/// can be used to update the data manually. Can be useful when used
|
||||||
|
|||||||
Reference in New Issue
Block a user