diff --git a/packages/fl_query/.flutter-plugins-dependencies b/packages/fl_query/.flutter-plugins-dependencies index 3e113a8..d727732 100644 --- a/packages/fl_query/.flutter-plugins-dependencies +++ b/packages/fl_query/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"connectivity_plus","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus-2.3.5/","native_build":true,"dependencies":[]}],"android":[{"name":"connectivity_plus","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus-2.3.5/","native_build":true,"dependencies":[]}],"macos":[{"name":"connectivity_plus_macos","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_macos-1.2.4/","native_build":true,"dependencies":[]}],"linux":[{"name":"connectivity_plus_linux","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_linux-1.3.1/","native_build":false,"dependencies":[]}],"windows":[{"name":"connectivity_plus_windows","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_windows-1.2.2/","native_build":true,"dependencies":[]}],"web":[{"name":"connectivity_plus_web","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_web-1.2.2/","dependencies":[]}]},"dependencyGraph":[{"name":"connectivity_plus","dependencies":["connectivity_plus_linux","connectivity_plus_macos","connectivity_plus_web","connectivity_plus_windows"]},{"name":"connectivity_plus_linux","dependencies":[]},{"name":"connectivity_plus_macos","dependencies":[]},{"name":"connectivity_plus_web","dependencies":[]},{"name":"connectivity_plus_windows","dependencies":[]}],"date_created":"2022-08-04 09:09:34.759333","version":"3.0.1"} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"connectivity_plus","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus-2.3.5/","native_build":true,"dependencies":[]}],"android":[{"name":"connectivity_plus","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus-2.3.5/","native_build":true,"dependencies":[]}],"macos":[{"name":"connectivity_plus_macos","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_macos-1.2.4/","native_build":true,"dependencies":[]}],"linux":[{"name":"connectivity_plus_linux","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_linux-1.3.1/","native_build":false,"dependencies":[]}],"windows":[{"name":"connectivity_plus_windows","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_windows-1.2.2/","native_build":true,"dependencies":[]}],"web":[{"name":"connectivity_plus_web","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_web-1.2.2/","dependencies":[]}]},"dependencyGraph":[{"name":"connectivity_plus","dependencies":["connectivity_plus_linux","connectivity_plus_macos","connectivity_plus_web","connectivity_plus_windows"]},{"name":"connectivity_plus_linux","dependencies":[]},{"name":"connectivity_plus_macos","dependencies":[]},{"name":"connectivity_plus_web","dependencies":[]},{"name":"connectivity_plus_windows","dependencies":[]}],"date_created":"2022-08-07 10:53:50.758755","version":"3.0.1"} \ No newline at end of file diff --git a/packages/fl_query/example/lib/components/query_previous_data.dart b/packages/fl_query/example/lib/components/query_previous_data.dart new file mode 100644 index 0000000..50a68b4 --- /dev/null +++ b/packages/fl_query/example/lib/components/query_previous_data.dart @@ -0,0 +1,70 @@ +import 'dart:convert'; + +import 'package:fl_query/fl_query.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; + +final todoJob = QueryJob.withVariableKey( + preQueryKey: "todo", + task: (queryKey, _) async { + final res = await http.get( + Uri.parse( + "https://jsonplaceholder.typicode.com/todos/${getVariable(queryKey)}"), + ); + return jsonDecode(res.body); + }, + keepPreviousData: true, +); + +class QueryPreviousDataExample extends StatefulWidget { + const QueryPreviousDataExample({Key? key}) : super(key: key); + + @override + State createState() => + _QueryPreviousDataExampleState(); +} + +class _QueryPreviousDataExampleState extends State { + int id = 1; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "# Query Variable Key with keepPreviousData", + style: Theme.of(context).textTheme.headline5, + ), + QueryBuilder( + job: todoJob(id.toString()), + externalData: null, + builder: (context, query) { + if (query.hasError) return Text(query.error.toString()); + if (!query.hasData) return const CircularProgressIndicator(); + return Text(jsonEncode(query.data ?? {})); + }), + Row( + children: [ + IconButton( + icon: const Icon(Icons.remove), + onPressed: () { + setState(() { + id -= 1; + }); + }, + ), + IconButton( + icon: const Icon(Icons.add), + onPressed: () { + setState(() { + id += 1; + }); + }, + ), + ], + ) + ], + ); + } +} diff --git a/packages/fl_query/example/lib/main.dart b/packages/fl_query/example/lib/main.dart index 9e6d19f..690264e 100644 --- a/packages/fl_query/example/lib/main.dart +++ b/packages/fl_query/example/lib/main.dart @@ -3,6 +3,7 @@ import 'package:fl_query_example/components/basic_query.dart'; import 'package:fl_query_example/components/lazy_query.dart'; import 'package:fl_query_example/components/mutation_variable_key.dart'; import 'package:fl_query_example/components/query_external_data.dart'; +import 'package:fl_query_example/components/query_previous_data.dart'; import 'package:fl_query_example/components/query_variable_key.dart'; import 'package:fl_query/fl_query.dart'; import 'package:flutter/material.dart'; @@ -53,6 +54,7 @@ class _MyHomePageState extends State with WidgetsBindingObserver { QueryExternalDataExample(), LazyQueryExample(), QueryVariableKeyExample(), + QueryPreviousDataExample(), Divider(), BasicMutationExample(), MutationVariableKeyExample(), diff --git a/packages/fl_query/lib/fl_query.dart b/packages/fl_query/lib/fl_query.dart index da15442..9c727cb 100644 --- a/packages/fl_query/lib/fl_query.dart +++ b/packages/fl_query/lib/fl_query.dart @@ -7,4 +7,4 @@ export 'src/mutation.dart'; export 'src/mutation_builder.dart'; export 'src/models/query_job.dart'; export 'src/models/mutation_job.dart'; -export 'src/utils.dart' show isShallowEqual; +export 'src/utils.dart' show isShallowEqual, getVariable; diff --git a/packages/fl_query/lib/src/models/query_job.dart b/packages/fl_query/lib/src/models/query_job.dart index 7e1ffcd..e0879f6 100644 --- a/packages/fl_query/lib/src/models/query_job.dart +++ b/packages/fl_query/lib/src/models/query_job.dart @@ -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 { // all params @@ -7,21 +8,25 @@ class QueryJob { QueryTaskFunction 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 { this.refetchOnReconnect, this.refetchOnExternalDataChange, this.connectivity, + this.keepPreviousData, }) : _queryKey = queryKey; String get queryKey => _queryKey; @@ -60,10 +66,11 @@ class QueryJob { bool? refetchOnReconnect, bool? refetchOnExternalDataChange, Connectivity? connectivity, + bool? keepPreviousData, }) { return (String queryKey) { if (preQueryKey != null) queryKey = "$preQueryKey#$queryKey"; - return QueryJob( + final query = QueryJob( queryKey: queryKey, task: task, retries: retries, @@ -77,7 +84,10 @@ class QueryJob { refetchOnReconnect: refetchOnReconnect, refetchOnExternalDataChange: refetchOnExternalDataChange, connectivity: connectivity, + keepPreviousData: keepPreviousData, ); + query.isDynamic = true; + return query; }; } } diff --git a/packages/fl_query/lib/src/query.dart b/packages/fl_query/lib/src/query.dart index cce63c4..9a667be 100644 --- a/packages/fl_query/lib/src/query.dart +++ b/packages/fl_query/lib/src/query.dart @@ -72,6 +72,8 @@ class Query extends BaseOperation { Connectivity _connectivity; + T? _previousData; + Query({ required this.queryKey, required this.task, @@ -85,6 +87,7 @@ class Query extends BaseOperation { this.refetchOnReconnect, this.refetchInterval, this.enabled = true, + T? previousData, Connectivity? connectivity, T? initialData, QueryListener? onData, @@ -92,9 +95,10 @@ class Query extends BaseOperation { }) : _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 extends BaseOperation { QueryJob options, { required super.queryBowl, required Outside externalData, + T? previousData, QueryListener? onData, QueryListener? onError, }) : queryKey = options.queryKey, @@ -118,13 +123,14 @@ class Query extends BaseOperation { 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 extends BaseOperation { _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 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 extends BaseOperation { }); } + /// 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 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 extends BaseOperation { 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 extends BaseOperation { 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 extends BaseOperation { 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 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 extends BaseOperation { 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() => this is A ? this as A : null; @@ -348,6 +398,10 @@ class Query extends BaseOperation { @override void mount(ValueKey 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(); diff --git a/packages/fl_query/lib/src/query_bowl.dart b/packages/fl_query/lib/src/query_bowl.dart index 887a58c..e0d1b03 100644 --- a/packages/fl_query/lib/src/query_bowl.dart +++ b/packages/fl_query/lib/src/query_bowl.dart @@ -284,11 +284,13 @@ class QueryBowl extends InheritedWidget { Query _createQueryWithDefaults( QueryJob options, - Outside externalData, - ) { + Outside externalData, [ + T? previousData, + ]) { final query = Query.fromOptions( options, externalData: externalData, + previousData: previousData, queryBowl: this, ); query.updateDefaultOptions( @@ -361,6 +363,7 @@ class QueryBowl extends InheritedWidget { required ValueKey key, final QueryListener? onData, final QueryListener? 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(queryJob, externalData); + final query = _createQueryWithDefaults( + queryJob, + externalData, + previousData, + ); if (onData != null) query.addDataListener(onData); if (onError != null) query.addErrorListener(onError); query.mount(key); diff --git a/packages/fl_query/lib/src/query_builder.dart b/packages/fl_query/lib/src/query_builder.dart index a764976..b4ea9fc 100644 --- a/packages/fl_query/lib/src/query_builder.dart +++ b/packages/fl_query/lib/src/query_builder.dart @@ -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 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( widget.job, externalData: widget.externalData, + previousData: previousData, key: uKey, onData: widget.onData, onError: widget.onError, @@ -72,7 +75,17 @@ class _QueryBuilderState // 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!)) { diff --git a/packages/fl_query/lib/src/utils.dart b/packages/fl_query/lib/src/utils.dart index 36f93cf..08bc52c 100644 --- a/packages/fl_query/lib/src/utils.dart +++ b/packages/fl_query/lib/src/utils.dart @@ -50,3 +50,7 @@ bool isConnectedToInternet(ConnectivityResult result) { ConnectivityResult.wifi, ].contains(result); } + +String getVariable(String queryKey) { + return queryKey.split("#").last; +} diff --git a/packages/fl_query/test/query_test.mocks.dart b/packages/fl_query/test/query_test.mocks.dart index 33fdfbb..8beffea 100644 --- a/packages/fl_query/test/query_test.mocks.dart +++ b/packages/fl_query/test/query_test.mocks.dart @@ -4,13 +4,14 @@ import 'dart:async' as _i7; -import 'package:connectivity_plus/connectivity_plus.dart' as _i10; +import 'package:connectivity_plus/connectivity_plus.dart' as _i11; import 'package:fl_query/src/models/mutation_job.dart' as _i9; import 'package:fl_query/src/models/query_job.dart' as _i8; import 'package:fl_query/src/mutation.dart' as _i4; import 'package:fl_query/src/query.dart' as _i3; import 'package:fl_query/src/query_bowl.dart' as _i6; import 'package:flutter/foundation.dart' as _i5; +import 'package:flutter/rendering.dart' as _i10; import 'package:flutter/widgets.dart' as _i2; import 'package:mockito/mockito.dart' as _i1; @@ -100,6 +101,14 @@ class MockQueryBowl extends _i1.Mock implements _i6.QueryBowl { _i2.Widget get child => (super.noSuchMethod(Invocation.getter(#child), returnValue: _FakeWidget_1()) as _i2.Widget); @override + _i7.Future prefetchQuery( + _i8.QueryJob? options, + {Outside? externalData}) => + (super.noSuchMethod( + Invocation.method( + #prefetchQuery, [options], {#externalData: externalData}), + returnValue: Future.value()) as _i7.Future); + @override _i7.Future fetchQuery( _i8.QueryJob? options, {Outside? externalData, @@ -122,7 +131,8 @@ class MockQueryBowl extends _i1.Mock implements _i6.QueryBowl { {Outside? externalData, _i2.ValueKey? key, _i3.QueryListener? onData, - _i3.QueryListener? onError}) => + _i3.QueryListener? onError, + T? previousData}) => (super.noSuchMethod( Invocation.method(#addQuery, [ queryJob @@ -130,7 +140,8 @@ class MockQueryBowl extends _i1.Mock implements _i6.QueryBowl { #externalData: externalData, #key: key, #onData: onData, - #onError: onError + #onError: onError, + #previousData: previousData }), returnValue: _FakeQuery_2()) as _i3.Query); @override @@ -190,7 +201,7 @@ class MockQueryBowl extends _i1.Mock implements _i6.QueryBowl { .noSuchMethod(Invocation.method(#toStringShort, []), returnValue: '') as String); @override - void debugFillProperties(_i5.DiagnosticPropertiesBuilder? properties) => + void debugFillProperties(_i10.DiagnosticPropertiesBuilder? properties) => super.noSuchMethod(Invocation.method(#debugFillProperties, [properties]), returnValueForMissingStub: null); @override @@ -232,22 +243,22 @@ class MockQueryBowl extends _i1.Mock implements _i6.QueryBowl { /// A class which mocks [Connectivity]. /// /// See the documentation for Mockito's code generation for more information. -class MockConnectivity extends _i1.Mock implements _i10.Connectivity { +class MockConnectivity extends _i1.Mock implements _i11.Connectivity { MockConnectivity() { _i1.throwOnMissingStub(this); } @override - _i7.Stream<_i10.ConnectivityResult> get onConnectivityChanged => + _i7.Stream<_i11.ConnectivityResult> get onConnectivityChanged => (super.noSuchMethod(Invocation.getter(#onConnectivityChanged), - returnValue: Stream<_i10.ConnectivityResult>.empty()) - as _i7.Stream<_i10.ConnectivityResult>); + returnValue: Stream<_i11.ConnectivityResult>.empty()) + as _i7.Stream<_i11.ConnectivityResult>); @override - _i7.Future<_i10.ConnectivityResult> checkConnectivity() => + _i7.Future<_i11.ConnectivityResult> checkConnectivity() => (super.noSuchMethod(Invocation.method(#checkConnectivity, []), - returnValue: Future<_i10.ConnectivityResult>.value( - _i10.ConnectivityResult.bluetooth)) - as _i7.Future<_i10.ConnectivityResult>); + returnValue: Future<_i11.ConnectivityResult>.value( + _i11.ConnectivityResult.bluetooth)) + as _i7.Future<_i11.ConnectivityResult>); } /// A class which mocks [QueryJob]. @@ -266,34 +277,16 @@ class MockQueryJobVoidObject extends _i1.Mock super.noSuchMethod(Invocation.setter(#task, _task), returnValueForMissingStub: null); @override - set refetchOnMount(bool? _refetchOnMount) => - super.noSuchMethod(Invocation.setter(#refetchOnMount, _refetchOnMount), + set initialData(Object? _initialData) => + super.noSuchMethod(Invocation.setter(#initialData, _initialData), returnValueForMissingStub: null); @override - set refetchOnReconnect(bool? _refetchOnReconnect) => super.noSuchMethod( - Invocation.setter(#refetchOnReconnect, _refetchOnReconnect), - returnValueForMissingStub: null); + bool get isDynamic => + (super.noSuchMethod(Invocation.getter(#isDynamic), returnValue: false) + as bool); @override - set refetchOnExternalDataChange(bool? _refetchOnExternalDataChange) => - super.noSuchMethod( - Invocation.setter( - #refetchOnExternalDataChange, _refetchOnExternalDataChange), - returnValueForMissingStub: null); - @override - set staleTime(Duration? _staleTime) => - super.noSuchMethod(Invocation.setter(#staleTime, _staleTime), - returnValueForMissingStub: null); - @override - set cacheTime(Duration? _cacheTime) => - super.noSuchMethod(Invocation.setter(#cacheTime, _cacheTime), - returnValueForMissingStub: null); - @override - set refetchInterval(Duration? _refetchInterval) => - super.noSuchMethod(Invocation.setter(#refetchInterval, _refetchInterval), - returnValueForMissingStub: null); - @override - set connectivity(_i10.Connectivity? _connectivity) => - super.noSuchMethod(Invocation.setter(#connectivity, _connectivity), + set isDynamic(bool? _isDynamic) => + super.noSuchMethod(Invocation.setter(#isDynamic, _isDynamic), returnValueForMissingStub: null); @override String get queryKey => diff --git a/packages/fl_query_hooks/example/lib/components/query_hook_previous_data.dart b/packages/fl_query_hooks/example/lib/components/query_hook_previous_data.dart new file mode 100644 index 0000000..c98a317 --- /dev/null +++ b/packages/fl_query_hooks/example/lib/components/query_hook_previous_data.dart @@ -0,0 +1,62 @@ +import 'dart:convert'; + +import 'package:fl_query/fl_query.dart'; +import 'package:fl_query_hooks/fl_query_hooks.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:http/http.dart' as http; + +final todoJob = QueryJob.withVariableKey( + preQueryKey: "todo", + task: (queryKey, _) async { + final res = await http.get( + Uri.parse( + "https://jsonplaceholder.typicode.com/todos/${getVariable(queryKey)}"), + ); + return jsonDecode(res.body); + }, + keepPreviousData: true, +); + +class QueryHookPreviousDataExample extends HookWidget { + const QueryHookPreviousDataExample({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final id = useState(1); + final query = + useQuery(job: todoJob(id.value.toString()), externalData: null); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "# Query Variable Key with keepPreviousData", + style: Theme.of(context).textTheme.headline5, + ), + if (query.hasError) + Text(query.error.toString()) + else if (!query.hasData) + const CircularProgressIndicator() + else + Text(jsonEncode(query.data ?? {})), + Row( + children: [ + IconButton( + icon: const Icon(Icons.remove), + onPressed: () { + id.value -= 1; + }, + ), + IconButton( + icon: const Icon(Icons.add), + onPressed: () { + id.value += 1; + }, + ), + ], + ) + ], + ); + } +} diff --git a/packages/fl_query_hooks/example/lib/main.dart b/packages/fl_query_hooks/example/lib/main.dart index 22467bc..d8297aa 100644 --- a/packages/fl_query_hooks/example/lib/main.dart +++ b/packages/fl_query_hooks/example/lib/main.dart @@ -3,6 +3,7 @@ import 'package:fl_query_hooks_example/components/basic_hook_query.dart'; import 'package:fl_query_hooks_example/components/lazy_hook_query.dart'; import 'package:fl_query_hooks_example/components/mutation_hook_variable_key.dart'; import 'package:fl_query_hooks_example/components/query_hook_external_data.dart'; +import 'package:fl_query_hooks_example/components/query_hook_previous_data.dart'; import 'package:fl_query_hooks_example/components/query_hook_variable_key.dart'; import 'package:fl_query/fl_query.dart'; import 'package:flutter/material.dart'; @@ -53,6 +54,7 @@ class _MyHomePageState extends State with WidgetsBindingObserver { QueryHookExternalDataExample(), LazyHookQueryExample(), QueryHookVariableKeyExample(), + QueryHookPreviousDataExample(), Divider(), BasicHookMutationExample(), MutationHookVariableKeyExample(), diff --git a/packages/fl_query_hooks/lib/src/use_query.dart b/packages/fl_query_hooks/lib/src/use_query.dart index 7bf1525..997db7f 100644 --- a/packages/fl_query_hooks/lib/src/use_query.dart +++ b/packages/fl_query_hooks/lib/src/use_query.dart @@ -1,3 +1,5 @@ +// ignore_for_file: invalid_use_of_protected_member + import 'package:fl_query/fl_query.dart'; import 'package:fl_query_hooks/src/utils.dart'; import 'package:flutter/widgets.dart'; @@ -31,10 +33,11 @@ Query useQuery({ final oldOnData = usePrevious(onData); final oldOnError = usePrevious(onError); - final init = useCallback(() { + final init = useCallback(([T? previousData]) { query.value = queryBowl.addQuery( job, externalData: externalData, + previousData: previousData, key: uKey, onData: onData, onError: onError, @@ -66,7 +69,17 @@ Query useQuery({ final hasOnDataChanged = oldOnData != onData && oldOnData != null; if (oldJob != null && oldJob.queryKey != job.queryKey) { disposeQuery(); - init(); + + /// setting the new query's initial data as prev query's data + /// when [job.keepPreviousData] is true and both are dynamic + if (oldJob.isDynamic && + job.isDynamic && + oldJob.keepPreviousData == true && + job.keepPreviousData == true) { + init(query.value.data); + } else { + init(); + } } else if (oldExternalData != null && externalData != null && !isShallowEqual(oldExternalData, externalData)) {