diff --git a/packages/fl_query/.flutter-plugins-dependencies b/packages/fl_query/.flutter-plugins-dependencies index d727732..456c8fd 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-07 10:53:50.758755","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-16 21:50:29.137318","version":"3.0.5"} \ No newline at end of file diff --git a/packages/fl_query/example/.flutter-plugins-dependencies b/packages/fl_query/example/.flutter-plugins-dependencies index bd6638d..65d57b1 100644 --- a/packages/fl_query/example/.flutter-plugins-dependencies +++ b/packages/fl_query/example/.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.6/","native_build":true,"dependencies":[]}],"android":[{"name":"connectivity_plus","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus-2.3.6/","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.3/","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:27:39.110033","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.6/","native_build":true,"dependencies":[]}],"android":[{"name":"connectivity_plus","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus-2.3.6/","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.3/","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-21 19:29:44.415263","version":"3.0.5"} \ No newline at end of file diff --git a/packages/fl_query/example/android/local.properties b/packages/fl_query/example/android/local.properties index dfe0f1d..d22f419 100644 --- a/packages/fl_query/example/android/local.properties +++ b/packages/fl_query/example/android/local.properties @@ -1,2 +1,2 @@ sdk.dir=/opt/android-sdk -flutter.sdk=/opt/flutter \ No newline at end of file +flutter.sdk=/home/krtirtho/fvm/versions/3.0.5 \ No newline at end of file diff --git a/packages/fl_query/example/ios/Flutter/Generated.xcconfig b/packages/fl_query/example/ios/Flutter/Generated.xcconfig index 0651718..4114276 100644 --- a/packages/fl_query/example/ios/Flutter/Generated.xcconfig +++ b/packages/fl_query/example/ios/Flutter/Generated.xcconfig @@ -1,5 +1,5 @@ // This is a generated file; do not edit or check into version control. -FLUTTER_ROOT=/opt/flutter +FLUTTER_ROOT=/home/krtirtho/fvm/versions/3.0.5 FLUTTER_APPLICATION_PATH=/home/krtirtho/dev/fl-query/packages/fl_query/example COCOAPODS_PARALLEL_CODE_SIGN=true FLUTTER_TARGET=lib/main.dart diff --git a/packages/fl_query/example/ios/Flutter/flutter_export_environment.sh b/packages/fl_query/example/ios/Flutter/flutter_export_environment.sh index dc91195..5e4fb33 100755 --- a/packages/fl_query/example/ios/Flutter/flutter_export_environment.sh +++ b/packages/fl_query/example/ios/Flutter/flutter_export_environment.sh @@ -1,6 +1,6 @@ #!/bin/sh # This is a generated file; do not edit or check into version control. -export "FLUTTER_ROOT=/opt/flutter" +export "FLUTTER_ROOT=/home/krtirtho/fvm/versions/3.0.5" export "FLUTTER_APPLICATION_PATH=/home/krtirtho/dev/fl-query/packages/fl_query/example" export "COCOAPODS_PARALLEL_CODE_SIGN=true" export "FLUTTER_TARGET=lib/main.dart" diff --git a/packages/fl_query/example/lib/components/basic_infinite_query.dart b/packages/fl_query/example/lib/components/basic_infinite_query.dart new file mode 100644 index 0000000..b8b5524 --- /dev/null +++ b/packages/fl_query/example/lib/components/basic_infinite_query.dart @@ -0,0 +1,62 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:fl_query/fl_query.dart'; +import 'package:http/http.dart' as http; + +final infiniteQueryJob = InfiniteQueryJob( + queryKey: "infinite-posts", + initialParam: 1, + task: (queryKey, pageParam, externalData) async { + return jsonDecode( + (await http.get( + Uri.parse("https://jsonplaceholder.typicode.com/posts/$pageParam"), + )) + .body, + ); + }, + getNextPageParam: (lastPage, lastParam) { + return lastParam + 1; + }, + getPreviousPageParam: (lastPage, lastParam) { + return lastParam - 1; + }, +); + +class BasicInfiniteQueryExample extends StatelessWidget { + const BasicInfiniteQueryExample({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(), + body: InfiniteQueryBuilder( + job: infiniteQueryJob, + externalData: null, + builder: (context, infiniteQuery) { + return Stack( + children: [ + ListView.builder( + itemCount: infiniteQuery.pages.length, + itemBuilder: (context, index) { + final page = infiniteQuery.pages[index]; + return ListTile( + title: Text(page?["title"] ?? ""), + subtitle: Text(page?["body"] ?? ""), + ); + }, + ), + Align( + alignment: Alignment.bottomRight, + child: IconButton( + icon: const Icon(Icons.get_app_rounded), + onPressed: () => infiniteQuery.fetchNextPage(), + ), + ), + ], + ); + }), + ); + } +} diff --git a/packages/fl_query/example/lib/main.dart b/packages/fl_query/example/lib/main.dart index 690264e..db9aa1b 100644 --- a/packages/fl_query/example/lib/main.dart +++ b/packages/fl_query/example/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:fl_query_example/components/basic_infinite_query.dart'; import 'package:fl_query_example/components/basic_mutation.dart'; import 'package:fl_query_example/components/basic_query.dart'; import 'package:fl_query_example/components/lazy_query.dart'; @@ -49,15 +50,26 @@ class _MyHomePageState extends State with WidgetsBindingObserver { child: Padding( padding: const EdgeInsets.all(8.0), child: Column( - children: const [ - BasicQueryExample(), - QueryExternalDataExample(), - LazyQueryExample(), - QueryVariableKeyExample(), - QueryPreviousDataExample(), - Divider(), - BasicMutationExample(), - MutationVariableKeyExample(), + children: [ + const BasicQueryExample(), + const QueryExternalDataExample(), + const LazyQueryExample(), + const QueryVariableKeyExample(), + const QueryPreviousDataExample(), + ListTile( + title: const Text("Infinite Query Example"), + trailing: const Icon(Icons.open_in_new), + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const BasicInfiniteQueryExample(), + ), + ); + }, + ), + const Divider(), + const BasicMutationExample(), + const MutationVariableKeyExample(), ], ), )), diff --git a/packages/fl_query/example/linux/flutter/ephemeral/generated_config.cmake b/packages/fl_query/example/linux/flutter/ephemeral/generated_config.cmake index f848afc..2bf6219 100644 --- a/packages/fl_query/example/linux/flutter/ephemeral/generated_config.cmake +++ b/packages/fl_query/example/linux/flutter/ephemeral/generated_config.cmake @@ -1,10 +1,10 @@ # Generated code do not commit. -file(TO_CMAKE_PATH "/opt/flutter" FLUTTER_ROOT) +file(TO_CMAKE_PATH "/home/krtirtho/fvm/versions/3.0.5" FLUTTER_ROOT) file(TO_CMAKE_PATH "/home/krtirtho/dev/fl-query/packages/fl_query/example" PROJECT_DIR) # Environment variables to pass to tool_backend.sh list(APPEND FLUTTER_TOOL_ENVIRONMENT - "FLUTTER_ROOT=/opt/flutter" + "FLUTTER_ROOT=/home/krtirtho/fvm/versions/3.0.5" "PROJECT_DIR=/home/krtirtho/dev/fl-query/packages/fl_query/example" "DART_DEFINES=RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==" "DART_OBFUSCATION=false" diff --git a/packages/fl_query/example/linux/flutter/ephemeral/libflutter_linux_gtk.so b/packages/fl_query/example/linux/flutter/ephemeral/libflutter_linux_gtk.so index 5511cd0..9cae4b1 100644 Binary files a/packages/fl_query/example/linux/flutter/ephemeral/libflutter_linux_gtk.so and b/packages/fl_query/example/linux/flutter/ephemeral/libflutter_linux_gtk.so differ diff --git a/packages/fl_query/example/macos/Flutter/ephemeral/Flutter-Generated.xcconfig b/packages/fl_query/example/macos/Flutter/ephemeral/Flutter-Generated.xcconfig index f9db3c7..afeceea 100644 --- a/packages/fl_query/example/macos/Flutter/ephemeral/Flutter-Generated.xcconfig +++ b/packages/fl_query/example/macos/Flutter/ephemeral/Flutter-Generated.xcconfig @@ -1,5 +1,5 @@ // This is a generated file; do not edit or check into version control. -FLUTTER_ROOT=/opt/flutter +FLUTTER_ROOT=/home/krtirtho/fvm/versions/3.0.5 FLUTTER_APPLICATION_PATH=/home/krtirtho/dev/fl-query/packages/fl_query/example COCOAPODS_PARALLEL_CODE_SIGN=true FLUTTER_BUILD_DIR=build diff --git a/packages/fl_query/example/macos/Flutter/ephemeral/flutter_export_environment.sh b/packages/fl_query/example/macos/Flutter/ephemeral/flutter_export_environment.sh index 430e089..1f836fd 100755 --- a/packages/fl_query/example/macos/Flutter/ephemeral/flutter_export_environment.sh +++ b/packages/fl_query/example/macos/Flutter/ephemeral/flutter_export_environment.sh @@ -1,6 +1,6 @@ #!/bin/sh # This is a generated file; do not edit or check into version control. -export "FLUTTER_ROOT=/opt/flutter" +export "FLUTTER_ROOT=/home/krtirtho/fvm/versions/3.0.5" export "FLUTTER_APPLICATION_PATH=/home/krtirtho/dev/fl-query/packages/fl_query/example" export "COCOAPODS_PARALLEL_CODE_SIGN=true" export "FLUTTER_BUILD_DIR=build" diff --git a/packages/fl_query/example/pubspec.lock b/packages/fl_query/example/pubspec.lock index 1c33ed1..963e39f 100644 --- a/packages/fl_query/example/pubspec.lock +++ b/packages/fl_query/example/pubspec.lock @@ -133,7 +133,7 @@ packages: path: ".." relative: true source: path - version: "0.1.0" + version: "0.2.0" flutter: dependency: "direct main" description: flutter @@ -170,6 +170,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.0.1" + infinite_scroll_pagination: + dependency: "direct main" + description: + name: infinite_scroll_pagination + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.0" js: dependency: transitive description: @@ -238,6 +245,13 @@ packages: description: flutter source: sdk version: "0.0.99" + sliver_tools: + dependency: transitive + description: + name: sliver_tools + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.7" source_span: dependency: transitive description: @@ -310,4 +324,4 @@ packages: version: "6.1.0" sdks: dart: ">=2.17.1 <3.0.0" - flutter: ">=1.20.0" + flutter: ">=1.22.0" diff --git a/packages/fl_query/example/pubspec.yaml b/packages/fl_query/example/pubspec.yaml index c8a7f07..7a04d3f 100644 --- a/packages/fl_query/example/pubspec.yaml +++ b/packages/fl_query/example/pubspec.yaml @@ -36,6 +36,7 @@ dependencies: cupertino_icons: ^1.0.2 fl_query: ^0.2.0 http: ^0.13.5 + infinite_scroll_pagination: ^3.2.0 dev_dependencies: flutter_test: diff --git a/packages/fl_query/lib/fl_query.dart b/packages/fl_query/lib/fl_query.dart index 9c727cb..f1b281d 100644 --- a/packages/fl_query/lib/fl_query.dart +++ b/packages/fl_query/lib/fl_query.dart @@ -1,10 +1,13 @@ library fl_query; +export 'src/infinite_query.dart'; +export 'src/infinite_query_builder.dart'; export 'src/query.dart'; export 'src/query_bowl.dart'; export 'src/query_builder.dart'; export 'src/mutation.dart'; export 'src/mutation_builder.dart'; +export 'src/models/infinite_query_job.dart'; export 'src/models/query_job.dart'; export 'src/models/mutation_job.dart'; export 'src/utils.dart' show isShallowEqual, getVariable; diff --git a/packages/fl_query/lib/src/infinite_query.dart b/packages/fl_query/lib/src/infinite_query.dart new file mode 100644 index 0000000..58fb654 --- /dev/null +++ b/packages/fl_query/lib/src/infinite_query.dart @@ -0,0 +1,135 @@ +import 'dart:async'; + +import 'package:fl_query/src/mixins/autocast.dart'; +import 'package:fl_query/src/models/infinite_query_job.dart'; +import 'package:flutter/widgets.dart'; + +typedef InfiniteQueryTaskFunction + = FutureOr Function( + String queryKey, + PageParam pageParam, + Outside externalData, +); +typedef InfiniteQueryPageParamFunction + = FutureOr Function(T lastPage, PageParam lastParam); + +class InfiniteQuery + with ChangeNotifier, AutoCast { + String queryKey; + + Map _data; + Map _error; + + InfiniteQueryTaskFunction task; + + InfiniteQueryPageParamFunction? getNextPageParam; + InfiniteQueryPageParamFunction? getPreviousPageParam; + + List get pageParams => _data.keys.toList(); + List get errors => _error.values.toList(); + List get pages => _data.values.toList(); + + PageParam currentParam; + + Outside _externalData; + + bool _hasNextPage = false; + bool _hasPreviousPage = false; + + bool get hasNextPage => _hasNextPage; + bool get hasPreviousPage => _hasPreviousPage; + + InfiniteQuery({ + required this.queryKey, + required this.task, + required PageParam initialParam, + required Outside externalData, + this.getNextPageParam, + this.getPreviousPageParam, + T? initialPage, + }) : currentParam = initialParam, + _externalData = externalData, + _error = {}, + _data = { + if (initialPage != null) initialParam: initialPage, + }; + + InfiniteQuery.fromOptions( + InfiniteQueryJob options, { + required Outside externalData, + }) : queryKey = options.queryKey, + task = options.task, + currentParam = options.initialParam, + _externalData = externalData, + _error = {}, + getNextPageParam = options.getNextPageParam, + getPreviousPageParam = options.getPreviousPageParam, + _data = { + if (options.initialPage != null) + options.initialParam: options.initialPage, + }; + + Future _execute() async { + final page = await task( + queryKey, + currentParam, + _externalData, + ); + _data[currentParam] = page; + notifyListeners(); + } + + Future> fetch() async { + if (_data.isEmpty) await _execute(); + return pages; + } + + Future fetchNextPage([ + InfiniteQueryPageParamFunction? getNextPageParam, + ]) async { + try { + if (_data[currentParam] == null) await _execute(); + final nextParam = await (getNextPageParam ?? this.getNextPageParam)?.call( + _data[currentParam]!, + currentParam, + ); + if (nextParam == null) { + _hasNextPage = false; + notifyListeners(); + return null; + } + _hasNextPage = true; + currentParam = nextParam; + return await _execute().then((_) => _data[currentParam]); + } catch (e) { + print("[InfiniteQuery.fetchNextPage]: $e"); + rethrow; + } + } + + Future fetchPreviousPage([ + InfiniteQueryPageParamFunction? getPreviousPageParam, + ]) async { + try { + if (_data[currentParam] == null) await _execute(); + final prevParam = + await (getNextPageParam ?? this.getPreviousPageParam)?.call( + _data[currentParam]!, + currentParam, + ); + if (prevParam == null) { + _hasPreviousPage = false; + notifyListeners(); + return null; + } + _hasPreviousPage = true; + currentParam = prevParam; + return await _execute().then((_) => _data[currentParam]); + } catch (e) { + print("[InfiniteQuery.fetchPreviousPage]: $e"); + rethrow; + } + } +} diff --git a/packages/fl_query/lib/src/infinite_query_builder.dart b/packages/fl_query/lib/src/infinite_query_builder.dart new file mode 100644 index 0000000..3f1519e --- /dev/null +++ b/packages/fl_query/lib/src/infinite_query_builder.dart @@ -0,0 +1,67 @@ +import 'package:fl_query/src/infinite_query.dart'; +import 'package:fl_query/src/models/infinite_query_job.dart'; +import 'package:fl_query/src/query_bowl.dart'; +import 'package:fl_query/src/utils.dart'; +import 'package:flutter/widgets.dart'; + +class InfiniteQueryBuilder + extends StatefulWidget { + final Function( + BuildContext context, + InfiniteQuery query, + ) builder; + final InfiniteQueryJob job; + final Outside externalData; + InfiniteQueryBuilder({ + required this.job, + required this.builder, + required this.externalData, + Key? key, + }) : super(key: key); + + @override + State> createState() => + _InfiniteQueryBuilderState(); +} + +class _InfiniteQueryBuilderState + extends State> { + InfiniteQuery? infiniteQuery; + late QueryBowl queryBowl; + late final ValueKey uKey; + + @override + void initState() { + super.initState(); + uKey = ValueKey(uuid.v4()); + WidgetsBinding.instance.addPostFrameCallback((_) => init()); + } + + void init([T? previousData]) async { + final bowl = QueryBowl.of(context); + infiniteQuery = bowl.addInfiniteQuery( + widget.job, + externalData: widget.externalData, + key: uKey, + ); + // final hasExternalDataChanged = infiniteQuery!.externalData != null && + // infiniteQuery!.prevUsedExternalData != null && + // !isShallowEqual(infiniteQuery!.externalData!, infiniteQuery!.prevUsedExternalData!); + // if (infiniteQuery!.fetched && hasExternalDataChanged) { + // await infiniteQuery!.refetch(); + // } else if (!infiniteQuery!.fetched) { + await infiniteQuery!.fetch(); + // } + } + + @override + Widget build(BuildContext context) { + queryBowl = QueryBowl.of(context); + final latestInfiniteQuery = queryBowl + .getInfiniteQuery(widget.job.queryKey) ?? + infiniteQuery; + if (latestInfiniteQuery == null) return Container(); + return widget.builder(context, latestInfiniteQuery); + } +} diff --git a/packages/fl_query/lib/src/mixins/autocast.dart b/packages/fl_query/lib/src/mixins/autocast.dart new file mode 100644 index 0000000..2227884 --- /dev/null +++ b/packages/fl_query/lib/src/mixins/autocast.dart @@ -0,0 +1,3 @@ +mixin AutoCast { + A? cast() => this is A ? this as A : null; +} diff --git a/packages/fl_query/lib/src/models/infinite_query_data.dart b/packages/fl_query/lib/src/models/infinite_query_data.dart new file mode 100644 index 0000000..adbe8e3 --- /dev/null +++ b/packages/fl_query/lib/src/models/infinite_query_data.dart @@ -0,0 +1,9 @@ +class InfiniteQueryData { + final Set pages; + final Set pageParams; + + InfiniteQueryData({ + required this.pages, + required this.pageParams, + }); +} diff --git a/packages/fl_query/lib/src/models/infinite_query_job.dart b/packages/fl_query/lib/src/models/infinite_query_job.dart new file mode 100644 index 0000000..5961f45 --- /dev/null +++ b/packages/fl_query/lib/src/models/infinite_query_job.dart @@ -0,0 +1,105 @@ +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:fl_query/src/infinite_query.dart'; +import 'package:flutter/widgets.dart'; + +class InfiniteQueryJob { + // all params + String _queryKey; + InfiniteQueryTaskFunction task; + final int? retries; + final Duration? retryDelay; + T? initialPage; + PageParam initialParam; + + /// 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 + final bool? refetchOnMount; + final bool? refetchOnReconnect; + final bool? refetchOnExternalDataChange; + final bool? keepPreviousData; + final Duration? staleTime; + final Duration? cacheTime; + + final Duration? refetchInterval; + final Connectivity? connectivity; + final InfiniteQueryPageParamFunction getNextPageParam; + final InfiniteQueryPageParamFunction getPreviousPageParam; + + @protected + bool isDynamic = false; + + InfiniteQueryJob({ + required String queryKey, + required this.task, + required this.initialParam, + required this.getNextPageParam, + required this.getPreviousPageParam, + this.retries, + this.retryDelay, + this.initialPage, + this.staleTime, + this.cacheTime, + this.enabled, + this.refetchInterval, + this.refetchOnMount, + this.refetchOnReconnect, + this.refetchOnExternalDataChange, + this.connectivity, + this.keepPreviousData, + }) : _queryKey = queryKey; + + String get queryKey => _queryKey; + + static InfiniteQueryJob Function(String queryKey) + withVariableKey({ + required InfiniteQueryTaskFunction task, + required InfiniteQueryPageParamFunction getNextPageParam, + required InfiniteQueryPageParamFunction getPreviousPageParam, + required PageParam initialParam, + + /// a extra key joined with queryKey by a '#' + /// + /// useful for matching a group query + String? preQueryKey, + int? retries, + Duration? retryDelay, + T? initialPage, + Duration? staleTime, + Duration? cacheTime, + bool? enabled, + Duration? refetchInterval, + bool? refetchOnMount, + bool? refetchOnReconnect, + bool? refetchOnExternalDataChange, + Connectivity? connectivity, + bool? keepPreviousData, + }) { + return (String queryKey) { + if (preQueryKey != null) queryKey = "$preQueryKey#$queryKey"; + final query = InfiniteQueryJob( + queryKey: queryKey, + task: task, + getNextPageParam: getNextPageParam, + getPreviousPageParam: getPreviousPageParam, + retries: retries, + retryDelay: retryDelay, + initialPage: initialPage, + staleTime: staleTime, + cacheTime: cacheTime, + enabled: enabled, + refetchInterval: refetchInterval, + refetchOnMount: refetchOnMount, + refetchOnReconnect: refetchOnReconnect, + refetchOnExternalDataChange: refetchOnExternalDataChange, + connectivity: connectivity, + keepPreviousData: keepPreviousData, + initialParam: initialParam, + ); + query.isDynamic = true; + return query; + }; + } +} diff --git a/packages/fl_query/lib/src/mutation.dart b/packages/fl_query/lib/src/mutation.dart index d3f7a5b..451a7e0 100644 --- a/packages/fl_query/lib/src/mutation.dart +++ b/packages/fl_query/lib/src/mutation.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:fl_query/fl_query.dart'; import 'package:fl_query/src/base_operation.dart'; +import 'package:fl_query/src/mixins/autocast.dart'; import 'package:fl_query/src/models/mutation_job.dart'; import 'package:flutter/widgets.dart'; @@ -23,7 +24,7 @@ typedef MutationListener = FutureOr Function( typedef MutationTaskFunction = FutureOr Function( String queryKey, V variables); -class Mutation extends BaseOperation { +class Mutation extends BaseOperation with AutoCast { // all params final String mutationKey; MutationTaskFunction task; @@ -201,8 +202,6 @@ class Mutation extends BaseOperation { _sideEffectContext = null; } - A? cast() => this is A ? this as A : null; - bool get isError => status == MutationStatus.error; bool get isIdle => status == MutationStatus.idle; bool get isLoading => status == MutationStatus.loading; diff --git a/packages/fl_query/lib/src/query.dart b/packages/fl_query/lib/src/query.dart index 9a667be..5d72dbc 100644 --- a/packages/fl_query/lib/src/query.dart +++ b/packages/fl_query/lib/src/query.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:fl_query/src/base_operation.dart'; +import 'package:fl_query/src/mixins/autocast.dart'; import 'package:fl_query/src/models/query_job.dart'; import 'package:fl_query/src/utils.dart'; import 'package:flutter/widgets.dart'; @@ -36,7 +37,7 @@ typedef ListenerUnsubscriber = void Function(); typedef QueryUpdateFunction = FutureOr Function(T? oldData); -class Query extends BaseOperation { +class Query extends BaseOperation with AutoCast { // all params final String queryKey; QueryTaskFunction task; @@ -234,7 +235,7 @@ class Query extends BaseOperation { /// 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] + /// the current available [data] (which can be nul if no [initialPage] /// was provided) instead of running the task to prevent race conditions Future fetch() async { if (!enabled) return null; @@ -391,8 +392,6 @@ class Query extends BaseOperation { return _previousData != null ? _previousData == data : false; } - A? cast() => this is A ? this as A : null; - String get debugLabel => "Query($queryKey)"; @override diff --git a/packages/fl_query/lib/src/query_bowl.dart b/packages/fl_query/lib/src/query_bowl.dart index e0d1b03..d6de68f 100644 --- a/packages/fl_query/lib/src/query_bowl.dart +++ b/packages/fl_query/lib/src/query_bowl.dart @@ -1,6 +1,8 @@ import 'dart:async'; import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:fl_query/src/infinite_query.dart'; +import 'package:fl_query/src/models/infinite_query_job.dart'; import 'package:fl_query/src/models/mutation_job.dart'; import 'package:fl_query/src/models/query_job.dart'; import 'package:fl_query/src/mutation.dart'; @@ -90,6 +92,7 @@ class QueryBowlScope extends StatefulWidget { } class _QueryBowlScopeState extends State { + late Set infiniteQueries; late Set queries; late Set mutations; @@ -98,6 +101,7 @@ class _QueryBowlScopeState extends State { @override void initState() { super.initState(); + infiniteQueries = {}; queries = {}; mutations = {}; @@ -122,6 +126,9 @@ class _QueryBowlScopeState extends State { } void _listenToUpdates() { + for (final infiniteQuery in infiniteQueries) { + infiniteQuery.addListener(() => updateInfiniteQueries(infiniteQuery)); + } for (final query in queries) { query.addListener(() => updateQueries(query)); } @@ -131,6 +138,9 @@ class _QueryBowlScopeState extends State { } void _disposeUpdateListeners() { + for (final infiniteQuery in infiniteQueries) { + infiniteQuery.removeListener(() => updateInfiniteQueries(infiniteQuery)); + } for (final query in queries) { query.removeListener(() => updateQueries(query)); } @@ -153,6 +163,21 @@ class _QueryBowlScopeState extends State { }); } + void updateInfiniteQueries(InfiniteQuery infiniteQuery) { + WidgetsBinding.instance.addPostFrameCallback((_) { + // checking & not including inactive queries + // basically garbage collecting queries + setState(() { + infiniteQueries = Set.from( + // infiniteQuery.isInactive + // ? infiniteQueries.where((el) => el.queryKey != infiniteQuery.queryKey) + // : infiniteQuery, + infiniteQueries, + ); + }); + }); + } + void updateMutations(Mutation mutation) { WidgetsBinding.instance.addPostFrameCallback((_) { setState(() { @@ -169,6 +194,15 @@ class _QueryBowlScopeState extends State { }); } + void addInfiniteQuery( + InfiniteQuery infiniteQuery) { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + infiniteQueries = Set.from({...infiniteQueries, infiniteQuery}); + }); + }); + } + void addQuery(Query query) { WidgetsBinding.instance.addPostFrameCallback((_) { setState(() { @@ -215,10 +249,12 @@ class _QueryBowlScopeState extends State { _disposeUpdateListeners(); _listenToUpdates(); return QueryBowl( + addInfiniteQuery: addInfiniteQuery, addQuery: addQuery, addMutation: addMutation, removeQueries: removeQueries, clear: clear, + infiniteQueries: infiniteQueries, queries: queries, mutations: mutations, staleTime: widget.staleTime, @@ -238,6 +274,7 @@ class _QueryBowlScopeState extends State { /// Its responsible or can be used for (not recommended) creating, /// updating & deleting queries & mutations class QueryBowl extends InheritedWidget { + final Set _infiniteQueries; final Set _queries; final Set _mutations; final Duration staleTime; @@ -248,6 +285,9 @@ class QueryBowl extends InheritedWidget { final bool refetchOnReconnect; final bool refetchOnExternalDataChange; + final void Function( + InfiniteQuery infiniteQuery) _addInfiniteQuery; + final void Function(Query query) _addQuery; @@ -260,11 +300,16 @@ class QueryBowl extends InheritedWidget { const QueryBowl({ required Widget child, + required final void Function( + InfiniteQuery infiniteQuery) + addInfiniteQuery, required final void Function( Query query) addQuery, required final void Function(Mutation mutation) addMutation, + required final Set infiniteQueries, required final Set queries, required final Set mutations, required this.staleTime, @@ -280,6 +325,8 @@ class QueryBowl extends InheritedWidget { _queries = queries, _mutations = mutations, _addMutation = addMutation, + _addInfiniteQuery = addInfiniteQuery, + _infiniteQueries = infiniteQueries, super(child: child, key: key); Query _createQueryWithDefaults( @@ -303,6 +350,26 @@ class QueryBowl extends InheritedWidget { return query; } + // InfiniteQuery _createInfiniteQueryWithDefaults< + // T extends Object, Outside, PageParam extends Object>( + // InfiniteQueryJob options, + // Outside externalData, + // ) { + // final query = InfiniteQuery.fromOptions( + // options, + // externalData: externalData, + // queryBowl: this, + // ); + // query.updateDefaultOptions( + // cacheTime: cacheTime, + // staleTime: staleTime, + // refetchInterval: refetchInterval, + // refetchOnMount: refetchOnMount, + // refetchOnReconnect: refetchOnReconnect, + // ); + // return query; + // } + Future prefetchQuery( QueryJob options, { required Outside externalData, @@ -355,6 +422,50 @@ class QueryBowl extends InheritedWidget { return await query.fetch(); } + @protected + InfiniteQuery + addInfiniteQuery( + InfiniteQueryJob infiniteQueryJob, { + required Outside externalData, + required ValueKey key, + final QueryListener? onData, + final QueryListener? onError, + }) { + final prevInfiniteQuery = _infiniteQueries.firstWhereOrNull( + (q) => q.queryKey == infiniteQueryJob.queryKey, + ); + if (prevInfiniteQuery is InfiniteQuery) { + // run the query if its still not called or if externalData has + // changed + // if (prevQuery.prevUsedExternalData != null && + // externalData != null && + // !isShallowEqual( + // prevQuery.prevUsedExternalData!, + // externalData, + // )) { + // prevQuery.setExternalData(externalData); + // } + // prevQuery.mount(key); + // if (onData != null) prevQuery.addDataListener(onData); + // if (onError != null) prevQuery.addErrorListener(onError); + // mounting the widget that is using the query in the prevQuery + return prevInfiniteQuery; + } + // _createQueryWithDefaults( + // infiniteQueryJob, + // externalData, + // ); + final infiniteQuery = InfiniteQuery.fromOptions( + infiniteQueryJob, + externalData: externalData, + ); + // if (onData != null) infiniteQuery.addDataListener(onData); + // if (onError != null) infiniteQuery.addErrorListener(onError); + // infiniteQuery.mount(key); + _addInfiniteQuery(infiniteQuery); + return infiniteQuery; + } + /// !⚠️**Warning** only for internal library usage @protected Query addQuery( @@ -428,6 +539,16 @@ class QueryBowl extends InheritedWidget { } } + InfiniteQuery? + getInfiniteQuery( + String queryKey, + ) { + return _infiniteQueries.firstWhereOrNull((infiniteQuery) { + return infiniteQuery.queryKey == queryKey && + infiniteQuery is InfiniteQuery; + })?.cast>(); + } + /// Get a query by providing queryKey only /// /// Useful for optimistic update or single query refetch @@ -519,6 +640,7 @@ class QueryBowl extends InheritedWidget { bool updateShouldNotify(QueryBowl oldWidget) { return oldWidget.staleTime != staleTime || oldWidget._queries != _queries || - oldWidget._mutations != _mutations; + oldWidget._mutations != _mutations || + oldWidget._infiniteQueries != _infiniteQueries; } } diff --git a/packages/fl_query_hooks/.flutter-plugins-dependencies b/packages/fl_query_hooks/.flutter-plugins-dependencies index 5813048..a50eb54 100644 --- a/packages/fl_query_hooks/.flutter-plugins-dependencies +++ b/packages/fl_query_hooks/.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.6/","native_build":true,"dependencies":[]}],"android":[{"name":"connectivity_plus","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus-2.3.6/","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.3/","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:33.749791","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.6/","native_build":true,"dependencies":[]}],"android":[{"name":"connectivity_plus","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus-2.3.6/","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.3/","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-16 21:50:27.976582","version":"3.0.5"} \ No newline at end of file diff --git a/packages/fl_query_hooks/example/.flutter-plugins-dependencies b/packages/fl_query_hooks/example/.flutter-plugins-dependencies index 7cdcd7c..693d931 100644 --- a/packages/fl_query_hooks/example/.flutter-plugins-dependencies +++ b/packages/fl_query_hooks/example/.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.6/","native_build":true,"dependencies":[]}],"android":[{"name":"connectivity_plus","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus-2.3.6/","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.3/","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:52:09.828374","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.6/","native_build":true,"dependencies":[]}],"android":[{"name":"connectivity_plus","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus-2.3.6/","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.3/","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-16 21:50:30.683860","version":"3.0.5"} \ No newline at end of file diff --git a/packages/fl_query_hooks/example/pubspec.lock b/packages/fl_query_hooks/example/pubspec.lock index d0c8f16..bb5bc48 100644 --- a/packages/fl_query_hooks/example/pubspec.lock +++ b/packages/fl_query_hooks/example/pubspec.lock @@ -133,14 +133,14 @@ packages: path: "../../fl_query" relative: true source: path - version: "0.1.0" + version: "0.2.0" fl_query_hooks: dependency: "direct main" description: path: ".." relative: true source: path - version: "0.1.0" + version: "0.2.0" flutter: dependency: "direct main" description: flutter