feat: add support for keepPreviousData & examples regarding this
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:fl_query/src/query.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class QueryJob<T extends Object, Outside> {
|
||||
// all params
|
||||
@@ -7,21 +8,25 @@ class QueryJob<T extends Object, Outside> {
|
||||
QueryTaskFunction<T, Outside> task;
|
||||
final int? retries;
|
||||
final Duration? retryDelay;
|
||||
final T? initialData;
|
||||
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
|
||||
bool? refetchOnMount;
|
||||
bool? refetchOnReconnect;
|
||||
bool? refetchOnExternalDataChange;
|
||||
Duration? staleTime;
|
||||
Duration? cacheTime;
|
||||
final bool? refetchOnMount;
|
||||
final bool? refetchOnReconnect;
|
||||
final bool? refetchOnExternalDataChange;
|
||||
final bool? keepPreviousData;
|
||||
final Duration? staleTime;
|
||||
final Duration? cacheTime;
|
||||
|
||||
Duration? refetchInterval;
|
||||
Connectivity? connectivity;
|
||||
final Duration? refetchInterval;
|
||||
final Connectivity? connectivity;
|
||||
|
||||
@protected
|
||||
bool isDynamic = false;
|
||||
|
||||
QueryJob({
|
||||
required String queryKey,
|
||||
@@ -37,6 +42,7 @@ class QueryJob<T extends Object, Outside> {
|
||||
this.refetchOnReconnect,
|
||||
this.refetchOnExternalDataChange,
|
||||
this.connectivity,
|
||||
this.keepPreviousData,
|
||||
}) : _queryKey = queryKey;
|
||||
|
||||
String get queryKey => _queryKey;
|
||||
@@ -60,10 +66,11 @@ class QueryJob<T extends Object, Outside> {
|
||||
bool? refetchOnReconnect,
|
||||
bool? refetchOnExternalDataChange,
|
||||
Connectivity? connectivity,
|
||||
bool? keepPreviousData,
|
||||
}) {
|
||||
return (String queryKey) {
|
||||
if (preQueryKey != null) queryKey = "$preQueryKey#$queryKey";
|
||||
return QueryJob<T, Outside>(
|
||||
final query = QueryJob<T, Outside>(
|
||||
queryKey: queryKey,
|
||||
task: task,
|
||||
retries: retries,
|
||||
@@ -77,7 +84,10 @@ class QueryJob<T extends Object, Outside> {
|
||||
refetchOnReconnect: refetchOnReconnect,
|
||||
refetchOnExternalDataChange: refetchOnExternalDataChange,
|
||||
connectivity: connectivity,
|
||||
keepPreviousData: keepPreviousData,
|
||||
);
|
||||
query.isDynamic = true;
|
||||
return query;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,8 @@ class Query<T extends Object, Outside> extends BaseOperation<T> {
|
||||
|
||||
Connectivity _connectivity;
|
||||
|
||||
T? _previousData;
|
||||
|
||||
Query({
|
||||
required this.queryKey,
|
||||
required this.task,
|
||||
@@ -85,6 +87,7 @@ class Query<T extends Object, Outside> extends BaseOperation<T> {
|
||||
this.refetchOnReconnect,
|
||||
this.refetchInterval,
|
||||
this.enabled = true,
|
||||
T? previousData,
|
||||
Connectivity? connectivity,
|
||||
T? initialData,
|
||||
QueryListener<T>? onData,
|
||||
@@ -92,9 +95,10 @@ class Query<T extends Object, Outside> extends BaseOperation<T> {
|
||||
}) : _staleTime = staleTime,
|
||||
_initialData = initialData,
|
||||
_externalData = externalData,
|
||||
status = QueryStatus.idle,
|
||||
status = previousData == null ? QueryStatus.idle : QueryStatus.success,
|
||||
_connectivity = connectivity ?? Connectivity(),
|
||||
super(data: initialData) {
|
||||
_previousData = previousData,
|
||||
super(data: previousData ?? initialData) {
|
||||
if (onData != null) _onDataListeners.add(onData);
|
||||
if (onError != null) _onErrorListeners.add(onError);
|
||||
|
||||
@@ -107,6 +111,7 @@ class Query<T extends Object, Outside> extends BaseOperation<T> {
|
||||
QueryJob<T, Outside> options, {
|
||||
required super.queryBowl,
|
||||
required Outside externalData,
|
||||
T? previousData,
|
||||
QueryListener<T>? onData,
|
||||
QueryListener<dynamic>? onError,
|
||||
}) : queryKey = options.queryKey,
|
||||
@@ -118,13 +123,14 @@ class Query<T extends Object, Outside> extends BaseOperation<T> {
|
||||
refetchInterval = options.refetchInterval,
|
||||
refetchOnMount = options.refetchOnMount,
|
||||
refetchOnReconnect = options.refetchOnReconnect,
|
||||
status = QueryStatus.idle,
|
||||
status = previousData == null ? QueryStatus.idle : QueryStatus.success,
|
||||
_connectivity = options.connectivity ?? Connectivity(),
|
||||
_previousData = previousData,
|
||||
super(
|
||||
cacheTime: options.cacheTime ?? const Duration(minutes: 5),
|
||||
retries: options.retries ?? 3,
|
||||
retryDelay: options.retryDelay ?? const Duration(milliseconds: 200),
|
||||
data: options.initialData,
|
||||
data: previousData ?? options.initialData,
|
||||
) {
|
||||
if (onData != null) _onDataListeners.add(onData);
|
||||
if (onError != null) _onErrorListeners.add(onError);
|
||||
@@ -222,12 +228,20 @@ class Query<T extends Object, Outside> extends BaseOperation<T> {
|
||||
_onErrorListeners.remove(listener);
|
||||
}
|
||||
|
||||
/// fetches data or runs the provided task initially
|
||||
///
|
||||
/// Once [data] is available it won't run the [task] ever again
|
||||
/// and will only return the available data
|
||||
///
|
||||
/// If a [fetch] is already running in the background it'll just return
|
||||
/// the current available [data] (which can be nul if no [initialData]
|
||||
/// was provided) instead of running the task to prevent race conditions
|
||||
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 (isLoading || isRefetching || hasData) return data;
|
||||
if (isLoading || isRefetching || (hasData && !isPreviousData)) return data;
|
||||
status = QueryStatus.loading;
|
||||
notifyListeners();
|
||||
return _execute().then((_) {
|
||||
@@ -236,6 +250,18 @@ class Query<T extends Object, Outside> extends BaseOperation<T> {
|
||||
});
|
||||
}
|
||||
|
||||
/// refetches a valid or invalid [Query]
|
||||
///
|
||||
/// When called before calling [fetch] in a [Query] it'll
|
||||
/// automatically run [fetch]
|
||||
///
|
||||
/// But if it's used to fetch the first data of a non-enabled [Query]
|
||||
/// aka `LazyQuery`, it'll execute the task & will set the status
|
||||
/// `enabled=true`
|
||||
///
|
||||
/// If a [refetch] is already running in the background it'll just return
|
||||
/// the current available [data] instead of running the task to prevent
|
||||
/// race conditions
|
||||
Future<T?> refetch() async {
|
||||
/// if isLoading/isRefetching is true that means its already fetching/
|
||||
/// refetching. So [_execute] again can create a race condition
|
||||
@@ -266,14 +292,22 @@ class Query<T extends Object, Outside> extends BaseOperation<T> {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Sets the [externalData] from outside of the query
|
||||
///
|
||||
/// Remember, it's for the very instance of [Query]
|
||||
/// So this won't persist through later UI/[Query] updates
|
||||
void setExternalData(Outside externalData) {
|
||||
_prevUsedExternalData = _externalData;
|
||||
_externalData = externalData;
|
||||
}
|
||||
|
||||
/// Resets the query
|
||||
///
|
||||
/// The values of internal state of the query are reset to the
|
||||
/// initial ones
|
||||
void reset() {
|
||||
refetchCount = 0;
|
||||
data = _initialData;
|
||||
data = _previousData ?? _initialData;
|
||||
error = null;
|
||||
fetched = false;
|
||||
status = QueryStatus.idle;
|
||||
@@ -283,8 +317,12 @@ class Query<T extends Object, Outside> extends BaseOperation<T> {
|
||||
mounts.clear();
|
||||
}
|
||||
|
||||
/// Update configurations of the query after already creating the Query
|
||||
/// instance
|
||||
/// Update configurations of the query
|
||||
/// after already creating the Query instance
|
||||
///
|
||||
/// Remember, it's just for the single query instance
|
||||
/// In the next UI update/render the options will get reset
|
||||
/// to the default ones defined in the [QueryJob] or [QueryBowlScope]
|
||||
void updateDefaultOptions({
|
||||
Duration? refetchInterval,
|
||||
Duration? staleTime,
|
||||
@@ -310,11 +348,20 @@ class Query<T extends Object, Outside> extends BaseOperation<T> {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// checks if the application is connected to internet in any mean
|
||||
///
|
||||
/// It's true when any one this is connected -
|
||||
/// - ethernet
|
||||
/// - mobile
|
||||
/// - wifi
|
||||
Future<bool> isInternetConnected() async {
|
||||
return isConnectedToInternet(await _connectivity.checkConnectivity());
|
||||
}
|
||||
|
||||
/// invalidates the query
|
||||
///
|
||||
/// Forcefully makes the query stale & expired which results in a refetch
|
||||
/// when met conditions
|
||||
void invalidate() {
|
||||
/// subtracting [staleTime] from [updatedAt] as staleTime=Duration.zero
|
||||
/// indicates the query must never become stale but subtracting the
|
||||
@@ -340,6 +387,9 @@ class Query<T extends Object, Outside> extends BaseOperation<T> {
|
||||
bool get isLoading => status == QueryStatus.loading;
|
||||
bool get isRefetching => status == QueryStatus.refetching;
|
||||
bool get isSuccess => status == QueryStatus.success;
|
||||
bool get isPreviousData {
|
||||
return _previousData != null ? _previousData == data : false;
|
||||
}
|
||||
|
||||
A? cast<A>() => this is A ? this as A : null;
|
||||
|
||||
@@ -348,6 +398,10 @@ class Query<T extends Object, Outside> extends BaseOperation<T> {
|
||||
@override
|
||||
void mount(ValueKey<String> uKey) {
|
||||
super.mount(uKey);
|
||||
|
||||
/// refetching on mount if it's set to true
|
||||
/// also checking if the is stale or not
|
||||
/// no need to refetch a valid query for no reason
|
||||
if (refetchOnMount == true && isStale) {
|
||||
this.isInternetConnected().then((isConnected) async {
|
||||
if (isConnected) await refetch();
|
||||
|
||||
@@ -284,11 +284,13 @@ class QueryBowl extends InheritedWidget {
|
||||
|
||||
Query<T, Outside> _createQueryWithDefaults<T extends Object, Outside>(
|
||||
QueryJob<T, Outside> options,
|
||||
Outside externalData,
|
||||
) {
|
||||
Outside externalData, [
|
||||
T? previousData,
|
||||
]) {
|
||||
final query = Query<T, Outside>.fromOptions(
|
||||
options,
|
||||
externalData: externalData,
|
||||
previousData: previousData,
|
||||
queryBowl: this,
|
||||
);
|
||||
query.updateDefaultOptions(
|
||||
@@ -361,6 +363,7 @@ class QueryBowl extends InheritedWidget {
|
||||
required ValueKey<String> key,
|
||||
final QueryListener<T>? onData,
|
||||
final QueryListener<dynamic>? onError,
|
||||
final T? previousData,
|
||||
}) {
|
||||
final prevQuery =
|
||||
_queries.firstWhereOrNull((q) => q.queryKey == queryJob.queryKey);
|
||||
@@ -381,7 +384,11 @@ class QueryBowl extends InheritedWidget {
|
||||
// mounting the widget that is using the query in the prevQuery
|
||||
return prevQuery;
|
||||
}
|
||||
final query = _createQueryWithDefaults<T, Outside>(queryJob, externalData);
|
||||
final query = _createQueryWithDefaults<T, Outside>(
|
||||
queryJob,
|
||||
externalData,
|
||||
previousData,
|
||||
);
|
||||
if (onData != null) query.addDataListener(onData);
|
||||
if (onError != null) query.addErrorListener(onError);
|
||||
query.mount(key);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// ignore_for_file: invalid_use_of_protected_member
|
||||
|
||||
import 'package:fl_query/src/models/query_job.dart';
|
||||
import 'package:fl_query/src/query.dart';
|
||||
import 'package:fl_query/src/query_bowl.dart';
|
||||
@@ -43,11 +45,12 @@ class _QueryBuilderState<T extends Object, Outside>
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => init());
|
||||
}
|
||||
|
||||
void init([QueryBowl? bowl]) async {
|
||||
bowl ??= QueryBowl.of(context);
|
||||
void init([T? previousData]) async {
|
||||
final bowl = QueryBowl.of(context);
|
||||
query = bowl.addQuery<T, Outside>(
|
||||
widget.job,
|
||||
externalData: widget.externalData,
|
||||
previousData: previousData,
|
||||
key: uKey,
|
||||
onData: widget.onData,
|
||||
onError: widget.onError,
|
||||
@@ -72,7 +75,17 @@ class _QueryBuilderState<T extends Object, Outside>
|
||||
// re-init the query-builder when new queryJob is appended
|
||||
if (oldWidget.job.queryKey != widget.job.queryKey) {
|
||||
_queryDispose();
|
||||
init();
|
||||
|
||||
/// setting the new query's initial data as prev query's data
|
||||
/// when [job.keepPreviousData] is true and both are dynamic
|
||||
if (oldWidget.job.isDynamic &&
|
||||
widget.job.isDynamic &&
|
||||
oldWidget.job.keepPreviousData == true &&
|
||||
widget.job.keepPreviousData == true) {
|
||||
init(query?.data);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
} else if (oldWidget.externalData != null &&
|
||||
widget.externalData != null &&
|
||||
!isShallowEqual(oldWidget.externalData!, widget.externalData!)) {
|
||||
|
||||
@@ -50,3 +50,7 @@ bool isConnectedToInternet(ConnectivityResult result) {
|
||||
ConnectivityResult.wifi,
|
||||
].contains(result);
|
||||
}
|
||||
|
||||
String getVariable(String queryKey) {
|
||||
return queryKey.split("#").last;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user