From efa2c81505385eba0ba45f50c0debb9e4708804b Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Tue, 21 Feb 2023 12:03:45 +0600 Subject: [PATCH] feat: add infinite query builder with example fix(query): updateQueryFn refetch when stale and update state of builders when widget is mounted --- packages/fl_query/.flutter-plugins | 20 +- .../fl_query/.flutter-plugins-dependencies | 2 +- packages/fl_query/example/lib/main.dart | 55 +---- .../fl_query/example/lib/models/product.dart | 92 +++++++++ packages/fl_query/example/lib/pages/home.dart | 27 +++ .../example/lib/pages/infinite_query.dart | 97 +++++++++ .../fl_query/example/lib/pages/query.dart | 62 ++++++ packages/fl_query/example/lib/router.dart | 22 ++ packages/fl_query/example/pubspec.lock | 36 +++- packages/fl_query/example/pubspec.yaml | 2 + packages/fl_query/lib/fl_query.dart | 1 + .../lib/src/collections/default_configs.dart | 4 +- packages/fl_query/lib/src/core/client.dart | 93 +++++++++ .../fl_query/lib/src/core/infinite_query.dart | 188 +++++++++++++----- packages/fl_query/lib/src/core/query.dart | 19 +- packages/fl_query/lib/src/core/retryer.dart | 18 +- .../src/widgets/infinite_query_builder.dart | 148 ++++++++++++++ .../lib/src/widgets/query_builder.dart | 11 +- .../widgets/state_notifier_listenable.dart | 2 +- packages/fl_query_hooks/.flutter-plugins | 20 +- .../.flutter-plugins-dependencies | 2 +- 21 files changed, 778 insertions(+), 143 deletions(-) create mode 100644 packages/fl_query/example/lib/models/product.dart create mode 100644 packages/fl_query/example/lib/pages/home.dart create mode 100644 packages/fl_query/example/lib/pages/infinite_query.dart create mode 100644 packages/fl_query/example/lib/pages/query.dart create mode 100644 packages/fl_query/example/lib/router.dart create mode 100644 packages/fl_query/lib/src/widgets/infinite_query_builder.dart diff --git a/packages/fl_query/.flutter-plugins b/packages/fl_query/.flutter-plugins index eec8974..d9cfa91 100644 --- a/packages/fl_query/.flutter-plugins +++ b/packages/fl_query/.flutter-plugins @@ -1,11 +1,11 @@ # This is a generated file; do not edit or check into version control. -connectivity_plus=/home/krtirtho/.pub-cache/hosted/pub.dev/connectivity_plus-2.3.9/ -connectivity_plus_linux=/home/krtirtho/.pub-cache/hosted/pub.dev/connectivity_plus_linux-1.3.1/ -connectivity_plus_macos=/home/krtirtho/.pub-cache/hosted/pub.dev/connectivity_plus_macos-1.2.6/ -connectivity_plus_web=/home/krtirtho/.pub-cache/hosted/pub.dev/connectivity_plus_web-1.2.5/ -connectivity_plus_windows=/home/krtirtho/.pub-cache/hosted/pub.dev/connectivity_plus_windows-1.2.2/ -path_provider=/home/krtirtho/.pub-cache/hosted/pub.dev/path_provider-2.0.12/ -path_provider_android=/home/krtirtho/.pub-cache/hosted/pub.dev/path_provider_android-2.0.22/ -path_provider_foundation=/home/krtirtho/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.1/ -path_provider_linux=/home/krtirtho/.pub-cache/hosted/pub.dev/path_provider_linux-2.1.7/ -path_provider_windows=/home/krtirtho/.pub-cache/hosted/pub.dev/path_provider_windows-2.1.3/ +connectivity_plus=/home/kingkor/.pub-cache/hosted/pub.dev/connectivity_plus-2.3.9/ +connectivity_plus_linux=/home/kingkor/.pub-cache/hosted/pub.dev/connectivity_plus_linux-1.3.1/ +connectivity_plus_macos=/home/kingkor/.pub-cache/hosted/pub.dev/connectivity_plus_macos-1.2.6/ +connectivity_plus_web=/home/kingkor/.pub-cache/hosted/pub.dev/connectivity_plus_web-1.2.5/ +connectivity_plus_windows=/home/kingkor/.pub-cache/hosted/pub.dev/connectivity_plus_windows-1.2.2/ +path_provider=/home/kingkor/.pub-cache/hosted/pub.dev/path_provider-2.0.12/ +path_provider_android=/home/kingkor/.pub-cache/hosted/pub.dev/path_provider_android-2.0.22/ +path_provider_foundation=/home/kingkor/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.1/ +path_provider_linux=/home/kingkor/.pub-cache/hosted/pub.dev/path_provider_linux-2.1.8/ +path_provider_windows=/home/kingkor/.pub-cache/hosted/pub.dev/path_provider_windows-2.1.3/ diff --git a/packages/fl_query/.flutter-plugins-dependencies b/packages/fl_query/.flutter-plugins-dependencies index d35a47d..8d0c185 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.dev/connectivity_plus-2.3.9/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/home/krtirtho/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.1/","native_build":true,"dependencies":[]}],"android":[{"name":"connectivity_plus","path":"/home/krtirtho/.pub-cache/hosted/pub.dev/connectivity_plus-2.3.9/","native_build":true,"dependencies":[]},{"name":"path_provider_android","path":"/home/krtirtho/.pub-cache/hosted/pub.dev/path_provider_android-2.0.22/","native_build":true,"dependencies":[]}],"macos":[{"name":"connectivity_plus_macos","path":"/home/krtirtho/.pub-cache/hosted/pub.dev/connectivity_plus_macos-1.2.6/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/home/krtirtho/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.1/","native_build":true,"dependencies":[]}],"linux":[{"name":"connectivity_plus_linux","path":"/home/krtirtho/.pub-cache/hosted/pub.dev/connectivity_plus_linux-1.3.1/","native_build":false,"dependencies":[]},{"name":"path_provider_linux","path":"/home/krtirtho/.pub-cache/hosted/pub.dev/path_provider_linux-2.1.7/","native_build":false,"dependencies":[]}],"windows":[{"name":"connectivity_plus_windows","path":"/home/krtirtho/.pub-cache/hosted/pub.dev/connectivity_plus_windows-1.2.2/","native_build":true,"dependencies":[]},{"name":"path_provider_windows","path":"/home/krtirtho/.pub-cache/hosted/pub.dev/path_provider_windows-2.1.3/","native_build":false,"dependencies":[]}],"web":[{"name":"connectivity_plus_web","path":"/home/krtirtho/.pub-cache/hosted/pub.dev/connectivity_plus_web-1.2.5/","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":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]}],"date_created":"2023-02-14 13:29:41.761045","version":"3.7.0"} \ 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/kingkor/.pub-cache/hosted/pub.dev/connectivity_plus-2.3.9/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/home/kingkor/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.1/","native_build":true,"dependencies":[]}],"android":[{"name":"connectivity_plus","path":"/home/kingkor/.pub-cache/hosted/pub.dev/connectivity_plus-2.3.9/","native_build":true,"dependencies":[]},{"name":"path_provider_android","path":"/home/kingkor/.pub-cache/hosted/pub.dev/path_provider_android-2.0.22/","native_build":true,"dependencies":[]}],"macos":[{"name":"connectivity_plus_macos","path":"/home/kingkor/.pub-cache/hosted/pub.dev/connectivity_plus_macos-1.2.6/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/home/kingkor/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.1/","native_build":true,"dependencies":[]}],"linux":[{"name":"connectivity_plus_linux","path":"/home/kingkor/.pub-cache/hosted/pub.dev/connectivity_plus_linux-1.3.1/","native_build":false,"dependencies":[]},{"name":"path_provider_linux","path":"/home/kingkor/.pub-cache/hosted/pub.dev/path_provider_linux-2.1.8/","native_build":false,"dependencies":[]}],"windows":[{"name":"connectivity_plus_windows","path":"/home/kingkor/.pub-cache/hosted/pub.dev/connectivity_plus_windows-1.2.2/","native_build":true,"dependencies":[]},{"name":"path_provider_windows","path":"/home/kingkor/.pub-cache/hosted/pub.dev/path_provider_windows-2.1.3/","native_build":false,"dependencies":[]}],"web":[{"name":"connectivity_plus_web","path":"/home/kingkor/.pub-cache/hosted/pub.dev/connectivity_plus_web-1.2.5/","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":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]}],"date_created":"2023-02-21 09:32:52.281806","version":"3.7.3"} \ No newline at end of file diff --git a/packages/fl_query/example/lib/main.dart b/packages/fl_query/example/lib/main.dart index 3e27f51..3a1596c 100644 --- a/packages/fl_query/example/lib/main.dart +++ b/packages/fl_query/example/lib/main.dart @@ -1,5 +1,4 @@ -import 'dart:math'; - +import 'package:example/router.dart'; import 'package:fl_query/fl_query.dart'; import 'package:flutter/material.dart'; @@ -18,54 +17,10 @@ class MainApp extends StatelessWidget { @override Widget build(BuildContext context) { - final value = Random().nextInt(200000); - return MaterialApp( - home: Scaffold( - floatingActionButton: QueryListenable( - const ValueKey('hello'), builder: (context, query) { - if (query == null) { - return const SizedBox(); - } - return FloatingActionButton( - onPressed: () { - query.refresh(); - }, - child: Text(query.data ?? 'No Data'), - ); - }), - body: QueryBuilder( - const ValueKey('hello'), - () { - return Future.delayed( - const Duration(seconds: 6), () => 'Hello World! $value'); - }, - initial: 'Hello', - jsonConfig: JsonConfig( - fromJson: (json) => json['data'], - toJson: (data) => {'data': data}, - ), - onData: (value) { - print('onData: $value'); - }, - onError: (error) { - print('onError: $error'); - }, - builder: (context, query) { - if (query.isLoading) { - return const Center( - child: CircularProgressIndicator(), - ); - } else if (query.hasError) { - return Center( - child: Text(query.error.toString()), - ); - } - return Center( - child: Text(query.data), - ); - }, - ), - ), + return MaterialApp.router( + title: 'FL Query Example', + showPerformanceOverlay: true, + routerConfig: router, ); } } diff --git a/packages/fl_query/example/lib/models/product.dart b/packages/fl_query/example/lib/models/product.dart new file mode 100644 index 0000000..f613d52 --- /dev/null +++ b/packages/fl_query/example/lib/models/product.dart @@ -0,0 +1,92 @@ +class Product { + int id; + String title; + String description; + double price; + double discountPercentage; + double rating; + double stock; + String brand; + String category; + String thumbnail; + List images; + + Product({ + required this.id, + required this.title, + required this.description, + required this.price, + required this.discountPercentage, + required this.rating, + required this.stock, + required this.brand, + required this.category, + required this.thumbnail, + required this.images, + }); + + Product.fromJson(Map json) + : id = json['id'], + title = json['title'], + description = json['description'], + price = json['price'].toDouble(), + discountPercentage = json['discountPercentage'].toDouble(), + rating = json['rating'].toDouble(), + stock = json['stock'].toDouble(), + brand = json['brand'], + category = json['category'], + thumbnail = json['thumbnail'], + images = json['images'].cast(); + + Map toJson() { + final Map data = { + 'id': id, + 'title': title, + 'description': description, + 'price': price, + 'discountPercentage': discountPercentage, + 'rating': rating, + 'stock': stock, + 'brand': brand, + 'category': category, + 'thumbnail': thumbnail, + 'images': images, + }; + return data; + } +} + +class PagedProducts { + List products; + int total; + int skip; + int limit; + + PagedProducts({ + this.products = const [], + required this.total, + required this.skip, + required this.limit, + }); + + PagedProducts.fromJson(Map json) + : products = json['products'] + ?.map((v) => Product.fromJson( + Map.castFrom(v), + )) + .toList() ?? + [], + total = json['total'], + skip = json['skip'], + limit = json['limit']; + + Map toJson() { + final Map data = { + 'products': products.map((v) => v.toJson()).toList(), + 'total': total, + 'skip': skip, + 'limit': limit, + }; + return data; + } +} diff --git a/packages/fl_query/example/lib/pages/home.dart b/packages/fl_query/example/lib/pages/home.dart new file mode 100644 index 0000000..03632fd --- /dev/null +++ b/packages/fl_query/example/lib/pages/home.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +class HomePage extends StatelessWidget { + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('FL Query Example'), + ), + body: ListView( + children: [ + ListTile( + title: const Text('Query'), + onTap: () => GoRouter.of(context).push('/query'), + ), + ListTile( + title: const Text('Infinite Query'), + onTap: () => GoRouter.of(context).push('/infinite-query'), + ), + ], + ), + ); + } +} diff --git a/packages/fl_query/example/lib/pages/infinite_query.dart b/packages/fl_query/example/lib/pages/infinite_query.dart new file mode 100644 index 0000000..cc13fcd --- /dev/null +++ b/packages/fl_query/example/lib/pages/infinite_query.dart @@ -0,0 +1,97 @@ +import 'dart:convert'; + +import 'package:example/models/product.dart'; +import 'package:fl_query/fl_query.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart'; + +class InfiniteQueryPageWidget extends StatefulWidget { + const InfiniteQueryPageWidget({super.key}); + + @override + State createState() => + _InfiniteQueryPageWidgetState(); +} + +class _InfiniteQueryPageWidgetState extends State { + final controller = ScrollController(); + + @override + void initState() { + super.initState(); + controller.addListener(() async { + if (controller.position.pixels == controller.position.maxScrollExtent) { + final query = QueryClient.of(context) + .getInfiniteQuery(const ValueKey("products")); + await query?.fetchNext(); + } + }); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Infinite Query'), + ), + body: InfiniteQueryBuilder( + const ValueKey("products"), + (page) async { + final res = await get(Uri.parse( + "https://dummyjson.com/products?limit=10&skip=${page * 10}", + )); + + if (res.statusCode == 200) { + return PagedProducts.fromJson(jsonDecode(res.body)); + } else { + throw ClientException(res.statusCode.toString(), res.request?.url); + } + }, + nextPage: (lastPage, pages) { + if (pages.isNotEmpty && + pages.last.products.length < pages[lastPage].limit) { + /// returning [null] will set [hasNextPage] to [false] + return null; + } + return lastPage + 1; + }, + initialPage: 0, + jsonConfig: JsonConfig( + fromJson: (json) => PagedProducts.fromJson(json), + toJson: (data) => data.toJson(), + ), + builder: (context, query) { + final products = query.pages.map((e) => e.products).expand((e) => e); + if (!query.hasPages) { + return const Center( + child: CircularProgressIndicator(), + ); + } + return ListView( + controller: controller, + children: [ + for (final product in products) + ListTile( + title: Text(product.title), + subtitle: Text(product.description), + leading: Image.network(product.thumbnail), + ), + if (query.hasNextPage) + const Center( + child: CircularProgressIndicator(), + ), + if (query.hasErrors) + ...query.errors.map((e) => Text(e.message)).toList(), + ], + ); + }, + ), + ); + } +} diff --git a/packages/fl_query/example/lib/pages/query.dart b/packages/fl_query/example/lib/pages/query.dart new file mode 100644 index 0000000..0d4fbae --- /dev/null +++ b/packages/fl_query/example/lib/pages/query.dart @@ -0,0 +1,62 @@ +import 'dart:math'; + +import 'package:fl_query/fl_query.dart'; +import 'package:flutter/material.dart'; + +class QueryPage extends StatelessWidget { + const QueryPage({super.key}); + + @override + Widget build(BuildContext context) { + final value = Random().nextInt(200000); + return Scaffold( + appBar: AppBar( + title: const Text('Query'), + ), + floatingActionButton: QueryListenable( + const ValueKey('hello'), builder: (context, query) { + if (query == null) { + return const SizedBox(); + } + return FloatingActionButton( + onPressed: () { + query.refresh(); + }, + child: Text(query.data ?? 'No Data'), + ); + }), + body: QueryBuilder( + const ValueKey('hello'), + () { + return Future.delayed( + const Duration(seconds: 6), () => 'Hello World! $value'); + }, + initial: 'Hello', + jsonConfig: JsonConfig( + fromJson: (json) => json['data'], + toJson: (data) => {'data': data}, + ), + onData: (value) { + debugPrint('onData: $value'); + }, + onError: (error) { + debugPrint('onError: $error'); + }, + builder: (context, query) { + if (query.isLoading) { + return const Center( + child: CircularProgressIndicator(), + ); + } else if (query.hasError) { + return Center( + child: Text(query.error.toString()), + ); + } + return Center( + child: Text(query.data ?? "Unfortunately, there's no data"), + ); + }, + ), + ); + } +} diff --git a/packages/fl_query/example/lib/router.dart b/packages/fl_query/example/lib/router.dart new file mode 100644 index 0000000..d5103a9 --- /dev/null +++ b/packages/fl_query/example/lib/router.dart @@ -0,0 +1,22 @@ +import "package:example/pages/home.dart"; +import "package:example/pages/infinite_query.dart"; +import "package:example/pages/query.dart"; +import "package:go_router/go_router.dart"; + +final router = GoRouter( + initialLocation: '/', + routes: [ + GoRoute( + path: '/', + builder: (context, state) => const HomePage(), + ), + GoRoute( + path: '/query', + builder: (context, state) => const QueryPage(), + ), + GoRoute( + path: '/infinite-query', + builder: (context, state) => const InfiniteQueryPageWidget(), + ), + ], +); diff --git a/packages/fl_query/example/pubspec.lock b/packages/fl_query/example/pubspec.lock index ae0f466..21dd23c 100644 --- a/packages/fl_query/example/pubspec.lock +++ b/packages/fl_query/example/pubspec.lock @@ -167,6 +167,14 @@ packages: description: flutter source: sdk version: "0.0.0" + go_router: + dependency: "direct main" + description: + name: go_router + sha256: "7a79232827c851f6b9f08c3c7254a1eaff7bc6aa147e103e51f951c5c10b0ccc" + url: "https://pub.dev" + source: hosted + version: "6.0.9" hive: dependency: transitive description: @@ -183,6 +191,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + http: + dependency: "direct main" + description: + name: http + sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482" + url: "https://pub.dev" + source: hosted + version: "0.13.5" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" js: dependency: transitive description: @@ -199,6 +223,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" + logging: + dependency: transitive + description: + name: logging + sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" + url: "https://pub.dev" + source: hosted + version: "1.1.1" matcher: dependency: transitive description: @@ -445,5 +477,5 @@ packages: source: hosted version: "6.2.2" sdks: - dart: ">=2.19.0 <4.0.0" - flutter: ">=3.0.0" + dart: ">=2.19.0 <3.0.0" + flutter: ">=3.3.0" diff --git a/packages/fl_query/example/pubspec.yaml b/packages/fl_query/example/pubspec.yaml index f6acd24..ef1c5e3 100644 --- a/packages/fl_query/example/pubspec.yaml +++ b/packages/fl_query/example/pubspec.yaml @@ -10,6 +10,8 @@ dependencies: flutter: sdk: flutter fl_query: ^0.3.1 + go_router: ^6.0.9 + http: ^0.13.5 dev_dependencies: flutter_test: diff --git a/packages/fl_query/lib/fl_query.dart b/packages/fl_query/lib/fl_query.dart index 094f00f..4210962 100644 --- a/packages/fl_query/lib/fl_query.dart +++ b/packages/fl_query/lib/fl_query.dart @@ -13,3 +13,4 @@ export 'src/core/query.dart'; export 'src/widgets/query_builder.dart'; export 'src/widgets/query_listenable.dart'; +export 'src/widgets/infinite_query_builder.dart'; diff --git a/packages/fl_query/lib/src/collections/default_configs.dart b/packages/fl_query/lib/src/collections/default_configs.dart index 1de53bd..6c697e3 100644 --- a/packages/fl_query/lib/src/collections/default_configs.dart +++ b/packages/fl_query/lib/src/collections/default_configs.dart @@ -8,9 +8,9 @@ abstract class DefaultConstants { ); static const RefreshConfig refreshConfig = RefreshConfig( - staleDuration: Duration(seconds: 10), + staleDuration: Duration(minutes: 2, milliseconds: 250), refreshInterval: Duration.zero, - refreshOnMount: true, + refreshOnMount: false, refreshOnQueryFnChange: false, ); diff --git a/packages/fl_query/lib/src/core/client.dart b/packages/fl_query/lib/src/core/client.dart index 3baf9c7..f25af16 100644 --- a/packages/fl_query/lib/src/core/client.dart +++ b/packages/fl_query/lib/src/core/client.dart @@ -4,6 +4,7 @@ import 'package:fl_query/src/collections/json_config.dart'; import 'package:fl_query/src/collections/refresh_config.dart'; import 'package:fl_query/src/collections/retry_config.dart'; import 'package:fl_query/src/core/cache.dart'; +import 'package:fl_query/src/core/infinite_query.dart'; import 'package:fl_query/src/core/provider.dart'; import 'package:fl_query/src/core/query.dart'; import 'package:flutter/material.dart'; @@ -83,6 +84,98 @@ class QueryClient { return await Future.wait(queries.map((query) => query.refresh())); } + InfiniteQuery + createInfiniteQuery( + ValueKey key, + InfiniteQueryFn queryFn, { + required InfiniteQueryNextPage nextPage, + required PageType initialParam, + RetryConfig retryConfig = DefaultConstants.retryConfig, + RefreshConfig refreshConfig = DefaultConstants.refreshConfig, + JsonConfig? jsonConfig, + }) { + final query = cache.infiniteQueries + .firstWhere( + (query) => query.key == key, + orElse: () => InfiniteQuery( + key, + queryFn, + nextPage: nextPage, + initialParam: initialParam, + retryConfig: retryConfig, + refreshConfig: refreshConfig, + jsonConfig: jsonConfig, + ), + ) + .cast(); + query.updateQueryFn(queryFn); + cache.addInfiniteQuery(query); + return query; + } + + Future fetchInfiniteQuery( + ValueKey key, InfiniteQueryFn queryFn, + {required InfiniteQueryNextPage nextPage, + required PageType initialParam, + RetryConfig retryConfig = DefaultConstants.retryConfig, + RefreshConfig refreshConfig = DefaultConstants.refreshConfig, + JsonConfig? jsonConfig}) async { + final query = createInfiniteQuery( + key, + queryFn, + nextPage: nextPage, + initialParam: initialParam, + retryConfig: retryConfig, + refreshConfig: refreshConfig, + jsonConfig: jsonConfig, + ); + return await query.fetch(); + } + + InfiniteQuery? + getInfiniteQuery( + ValueKey key) { + return cache.infiniteQueries + .firstWhereOrNull((query) => query.key == key) + ?.cast(); + } + + List getInfiniteQueries(List keys) { + return cache.infiniteQueries + .where((query) => keys.contains(query.key)) + .toList(); + } + + Future + refreshInfiniteQuery( + ValueKey key, + [PageType? page]) async { + final query = getInfiniteQuery(key); + if (query == null) return null; + return await query.refresh(page); + } + + Future?> + refreshInfiniteQueryAllPages( + ValueKey key) async { + final query = getInfiniteQuery(key); + if (query == null) return []; + return await query.refreshAll(); + } + + Future refreshInfiniteQueries(List keys) async { + final queries = getInfiniteQueries(keys); + return await Future.wait(queries.map((query) => query.refresh())); + } + + Future> refreshInfiniteQueriesAllPages( + List keys) async { + final queries = getInfiniteQueries(keys); + return await Future.wait(queries.map( + (query) async => MapEntry(query.key, await query.refreshAll()))) + .then((qs) => Map.fromEntries(qs)); + } + static QueryClient of(BuildContext context) { return context .dependOnInheritedWidgetOfExactType()! diff --git a/packages/fl_query/lib/src/core/infinite_query.dart b/packages/fl_query/lib/src/core/infinite_query.dart index 24b3405..54c5ab1 100644 --- a/packages/fl_query/lib/src/core/infinite_query.dart +++ b/packages/fl_query/lib/src/core/infinite_query.dart @@ -64,17 +64,20 @@ class InfiniteQueryPage { class InfiniteQueryState { final Set> pages; final InfiniteQueryFn queryFn; - final InfiniteQueryNextPage nextPage; + final InfiniteQueryNextPage _nextPage; const InfiniteQueryState({ required this.pages, required this.queryFn, - required this.nextPage, - }); + required InfiniteQueryNextPage nextPage, + }) : _nextPage = nextPage; PageType get lastPage => pages.last.page; - PageType? get getNextPage => - nextPage(lastPage, pages.map((e) => e.data!).toList()); + PageType? get getNextPage => _nextPage( + lastPage, + pages.map((e) => e.data).whereType().toList(), + ); + bool get hasNextPage => getNextPage != null; InfiniteQueryState copyWith({ @@ -85,11 +88,23 @@ class InfiniteQueryState { return InfiniteQueryState( pages: pages ?? this.pages, queryFn: queryFn ?? this.queryFn, - nextPage: nextPage ?? this.nextPage, + nextPage: nextPage ?? this._nextPage, ); } } +class PageEvent { + final P page; + final T data; + const PageEvent(this.page, this.data); + + factory PageEvent.fromPage( + InfiniteQueryPage page, + ) { + return PageEvent(page.page as P, page.data as T); + } +} + class InfiniteQuery extends StateNotifier> with Retryer { @@ -106,7 +121,9 @@ class InfiniteQuery this.retryConfig = DefaultConstants.retryConfig, this.refreshConfig = DefaultConstants.refreshConfig, this.jsonConfig, - }) : super(InfiniteQueryState( + }) : _dataController = StreamController.broadcast(), + _errorController = StreamController.broadcast(), + super(InfiniteQueryState( pages: { InfiniteQueryPage( page: initialParam, @@ -119,17 +136,30 @@ class InfiniteQuery )) { if (jsonConfig != null) { _mutex.protect(() async { - final json = await _box.get(key.toString()); + final Map? json = await _box.get(key.value); if (json != null) { state = state.copyWith( - pages: json.map( - (key, value) => MapEntry( - key as PageType, - jsonConfig!.fromJson(value), - ), - ), + pages: json.entries + .map( + (entry) => InfiniteQueryPage( + page: entry.key as PageType, + data: jsonConfig!.fromJson( + Map.castFrom( + entry.value, + )), + // this makes the page loaded from cache `stale` + updatedAt: + DateTime.now().subtract(refreshConfig.staleDuration), + staleDuration: refreshConfig.staleDuration, + ), + ) + .toSet(), ); } + }).then((_) { + if (hasListeners) { + return fetch(); + } }); } if (refreshConfig.refreshInterval > Duration.zero) @@ -146,12 +176,18 @@ class InfiniteQuery final _mutex = Mutex(); final _box = Hive.lazyBox("cache"); + final StreamController> _dataController; + final StreamController> _errorController; List get pages => state.pages.map((e) => e.data).whereType().toList(); List get errors => state.pages.map((e) => e.error).whereType().toList(); PageType get lastPage => state.lastPage; + Stream> get dataStream => + _dataController.stream; + Stream> get errorStream => + _errorController.stream; bool get isLoadingPage => !hasPageData && !hasPageError && _mutex.isLocked; bool get isRefreshingPage => (hasPageData || hasPageError) && _mutex.isLocked; @@ -160,8 +196,8 @@ class InfiniteQuery bool get hasPages => pages.isNotEmpty; bool get hasErrors => errors.isNotEmpty; - bool get hasPageData => state.pages.last.data != null; - bool get hasPageError => state.pages.last.error != null; + bool get hasPageData => !hasPages ? false : state.pages.last.data != null; + bool get hasPageError => !hasPages ? false : state.pages.last.error != null; bool get hasNextPage => state.hasNextPage; @@ -171,51 +207,54 @@ class InfiniteQuery () => state.queryFn(page), config: retryConfig, onSuccessful: (data) async { + final dataPage = state.pages + .firstWhere( + (e) => e.page == page, + orElse: () => InfiniteQueryPage( + page: page, + updatedAt: DateTime.now(), + staleDuration: refreshConfig.staleDuration, + ), + ) + .copyWith(data: data); state = state.copyWith( - pages: { - ...state.pages, - state.pages - .firstWhere( - (e) => e.page == page, - orElse: () => - InfiniteQueryPage( - page: page, - updatedAt: DateTime.now(), - staleDuration: refreshConfig.staleDuration, - ), - ) - .copyWith(data: data), - }, + pages: {...state.pages..remove(dataPage), dataPage}, ); + if (dataPage.data != null) + _dataController.add(PageEvent.fromPage(dataPage)); if (jsonConfig != null) { await _box.put( - key.toString(), - state.pages.map( - (e) => MapEntry( - e.page, - e.data != null ? jsonConfig!.toJson(e.data!) : null, + key.value, + Map.fromEntries( + state.pages.map( + (e) => MapEntry( + e.page, + e.data != null ? jsonConfig!.toJson(e.data!) : null, + ), ), ), ); } }, onFailed: (error) { + final errorPage = state.pages + .firstWhere( + (e) => e.page == page, + orElse: () => InfiniteQueryPage( + page: page, + updatedAt: DateTime.now(), + staleDuration: refreshConfig.staleDuration, + ), + ) + .copyWith(error: error); state = state.copyWith( pages: { - ...state.pages, - state.pages - .firstWhere( - (e) => e.page == page, - orElse: () => - InfiniteQueryPage( - page: page, - updatedAt: DateTime.now(), - staleDuration: refreshConfig.staleDuration, - ), - ) - .copyWith(error: error), + ...state.pages..remove(errorPage), + errorPage, }, ); + if (errorPage.error != null) + _errorController.add(PageEvent.fromPage(errorPage)); }, ); }); @@ -246,14 +285,54 @@ class InfiniteQuery Future fetchNext() async { final nextPage = state.getNextPage; - if (_mutex.isLocked || nextPage != null) { - return state.pages.firstWhereOrNull((e) => e.page == nextPage)?.data; + if (_mutex.isLocked || nextPage == null) { + return state.pages.lastOrNull?.data; } - return await _operation(nextPage!).then((_) { + return await _operation(nextPage).then((_) { return state.pages.firstWhereOrNull((e) => e.page == nextPage)?.data; }); } + void updateQueryFn(InfiniteQueryFn queryFn) { + if (state.queryFn == queryFn) return; + state = state.copyWith(queryFn: queryFn); + if (refreshConfig.refreshOnQueryFnChange) { + refreshAll(); + } else { + Future.wait( + state.pages.map((page) async { + if (page.isStale) { + return await refresh(page.page); + } + }), + ); + } + } + + void updateNextPageFn(InfiniteQueryNextPage nextPage) { + state = state.copyWith(nextPage: nextPage); + } + + void setPageData(PageType page, DataType data) { + final newPage = state.pages + .firstWhere( + (e) => e.page == page, + orElse: () => InfiniteQueryPage( + page: page, + updatedAt: DateTime.now(), + staleDuration: refreshConfig.staleDuration, + ), + ) + .copyWith(data: data); + + state = state.copyWith( + pages: { + ...state.pages..remove(newPage), + newPage, + }, + ); + } + @override RemoveListener addListener( Listener> listener, { @@ -276,4 +355,13 @@ class InfiniteQuery @override operator ==(Object other) => identical(this, other) || other is InfiniteQuery && key == other.key; + + @override + int get hashCode => key.hashCode; + + InfiniteQuery + cast() { + return this + as InfiniteQuery; + } } diff --git a/packages/fl_query/lib/src/core/query.dart b/packages/fl_query/lib/src/core/query.dart index 5043dce..6f2fda4 100644 --- a/packages/fl_query/lib/src/core/query.dart +++ b/packages/fl_query/lib/src/core/query.dart @@ -35,10 +35,11 @@ class QueryState { QueryState copyWith({ DataType? data, ErrorType? error, + DateTime? updatedAt, QueryFn? queryFn, }) { return QueryState( - updatedAt: DateTime.now(), + updatedAt: updatedAt ?? this.updatedAt, staleDuration: staleDuration, data: data ?? this.data, error: error ?? this.error, @@ -80,7 +81,7 @@ class Query _initial = jsonConfig!.fromJson( Map.castFrom(json), ); - state = state.copyWith(data: _initial); + state = state.copyWith(data: _initial, updatedAt: DateTime.now()); } }).then((_) { if (hasListeners) { @@ -124,7 +125,10 @@ class Query state.queryFn, config: retryConfig, onSuccessful: (DataType? data) { - state = state.copyWith(data: data); + state = state.copyWith( + data: data, + updatedAt: DateTime.now(), + ); if (data != null) _dataController.add(data); if (jsonConfig != null && data != null) { _box.put( @@ -134,7 +138,7 @@ class Query } }, onFailed: (ErrorType? error) { - state = state.copyWith(error: error); + state = state.copyWith(error: error, updatedAt: DateTime.now()); if (error != null) _errorController.add(error); }, ); @@ -154,17 +158,14 @@ class Query void updateQueryFn(QueryFn queryFn) { if (state.queryFn == queryFn) return; - // updatedAt is updated with copyWith so storing it - // here to check if the query is stale later - final stale = state.isStale; state = state.copyWith(queryFn: queryFn); - if (stale || refreshConfig.refreshOnQueryFnChange) { + if (state.isStale || refreshConfig.refreshOnQueryFnChange) { refresh(); } } void setData(DataType data) { - state = state.copyWith(data: data); + state = state.copyWith(data: data, updatedAt: DateTime.now()); } @override diff --git a/packages/fl_query/lib/src/core/retryer.dart b/packages/fl_query/lib/src/core/retryer.dart index be722df..c00d026 100644 --- a/packages/fl_query/lib/src/core/retryer.dart +++ b/packages/fl_query/lib/src/core/retryer.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:fl_query/src/collections/retry_config.dart'; +import 'package:flutter/material.dart'; mixin Retryer { void retryOperation( @@ -19,9 +20,20 @@ mixin Retryer { final result = await completer.future; onSuccessful(result); break; - } catch (e) { - if (attempts == config.maxRetries - 1 && e is E?) { - onFailed(e as E?); + } catch (e, stack) { + if (attempts == config.maxRetries - 1) { + if (e is E?) { + onFailed(e as E?); + } else { + FlutterError.reportError( + FlutterErrorDetails( + exception: e, + library: 'fl_query', + context: ErrorDescription('retryOperation'), + stack: stack, + ), + ); + } } } } diff --git a/packages/fl_query/lib/src/widgets/infinite_query_builder.dart b/packages/fl_query/lib/src/widgets/infinite_query_builder.dart new file mode 100644 index 0000000..b2c302a --- /dev/null +++ b/packages/fl_query/lib/src/widgets/infinite_query_builder.dart @@ -0,0 +1,148 @@ +import 'dart:async'; + +import 'package:fl_query/src/collections/default_configs.dart'; +import 'package:fl_query/src/collections/json_config.dart'; +import 'package:fl_query/src/collections/refresh_config.dart'; +import 'package:fl_query/src/collections/retry_config.dart'; +import 'package:fl_query/src/core/client.dart'; +import 'package:fl_query/src/core/infinite_query.dart'; +import 'package:flutter/material.dart'; + +typedef InfiniteQueryBuilderFn = Widget + Function( + BuildContext context, + InfiniteQuery query, +); + +class InfiniteQueryBuilder + extends StatefulWidget { + final InfiniteQueryFn queryFn; + final ValueKey queryKey; + + final PageType initialPage; + final InfiniteQueryNextPage nextPage; + + final RetryConfig retryConfig; + final RefreshConfig refreshConfig; + final JsonConfig? jsonConfig; + + final ValueChanged>? onData; + final ValueChanged>? onError; + + // widget specific + final bool enabled; + final InfiniteQueryBuilderFn builder; + + const InfiniteQueryBuilder( + this.queryKey, + this.queryFn, { + required this.nextPage, + required this.builder, + required this.initialPage, + this.retryConfig = DefaultConstants.retryConfig, + this.refreshConfig = DefaultConstants.refreshConfig, + this.jsonConfig, + this.onData, + this.onError, + this.enabled = true, + super.key, + }) : assert( + (jsonConfig != null && enabled) || jsonConfig == null, + 'jsonConfig is only supported when enabled is true', + ); + + @override + State> + createState() => + _InfiniteQueryBuilderState(); +} + +class _InfiniteQueryBuilderState + extends State< + InfiniteQueryBuilder> { + InfiniteQuery? query; + + VoidCallback? removeListener; + + StreamSubscription>? dataSubscription; + StreamSubscription>? errorSubscription; + + void update(_) { + if (mounted) setState(() {}); + } + + Future initialize() async { + setState(() { + query = QueryClient.of(context).createInfiniteQuery( + widget.queryKey, + widget.queryFn, + initialParam: widget.initialPage, + nextPage: widget.nextPage, + retryConfig: widget.retryConfig, + refreshConfig: widget.refreshConfig, + jsonConfig: widget.jsonConfig, + ); + + dataSubscription = query!.dataStream.listen(widget.onData); + errorSubscription = query!.errorStream.listen(widget.onError); + + removeListener = query!.addListener(update); + }); + if (widget.enabled) { + await query!.fetch(); + } + } + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + initialize(); + }); + } + + @override + void dispose() { + dataSubscription?.cancel(); + errorSubscription?.cancel(); + removeListener?.call(); + super.dispose(); + } + + @override + void didUpdateWidget(oldWidget) { + super.didUpdateWidget(oldWidget); + + if (oldWidget.queryKey != widget.queryKey) { + dataSubscription?.cancel(); + errorSubscription?.cancel(); + removeListener?.call(); + initialize(); + return; + } else if (oldWidget.enabled != widget.enabled && widget.enabled) { + query!.fetch(); + } + if (oldWidget.queryFn != widget.queryFn) { + query!.updateQueryFn(widget.queryFn); + } + if (oldWidget.nextPage != widget.nextPage) { + query!.updateNextPageFn(widget.nextPage); + } + if (oldWidget.onData != widget.onData) { + dataSubscription?.cancel(); + dataSubscription = query!.dataStream.listen(widget.onData); + } + if (oldWidget.onError != widget.onError) { + errorSubscription?.cancel(); + errorSubscription = query!.errorStream.listen(widget.onError); + } + } + + @override + Widget build(BuildContext context) { + if (query == null) { + return const SizedBox.shrink(); + } + return widget.builder(context, query!); + } +} diff --git a/packages/fl_query/lib/src/widgets/query_builder.dart b/packages/fl_query/lib/src/widgets/query_builder.dart index 09cbeeb..8400e7e 100644 --- a/packages/fl_query/lib/src/widgets/query_builder.dart +++ b/packages/fl_query/lib/src/widgets/query_builder.dart @@ -18,7 +18,6 @@ class QueryBuilder extends StatefulWidget { final QueryFn queryFn; final ValueKey queryKey; - final QueryBuilderFn builder; final DataType? initial; final RetryConfig retryConfig; @@ -30,6 +29,7 @@ class QueryBuilder extends StatefulWidget { // widget specific final bool enabled; + final QueryBuilderFn builder; const QueryBuilder( this.queryKey, @@ -44,7 +44,7 @@ class QueryBuilder extends StatefulWidget { this.enabled = true, super.key, }) : assert( - enabled && jsonConfig != null, + (jsonConfig != null && enabled) || jsonConfig == null, 'jsonConfig is only supported when enabled is true', ); @@ -63,7 +63,7 @@ class _QueryBuilderState StreamSubscription? errorSubscription; void update(_) { - setState(() {}); + if (mounted) setState(() {}); } Future initialize() async { @@ -115,6 +115,8 @@ class _QueryBuilderState removeListener?.call(); initialize(); return; + } else if (oldWidget.enabled != widget.enabled && widget.enabled) { + query!.fetch(); } if (oldWidget.queryFn != widget.queryFn) { query!.updateQueryFn(widget.queryFn); @@ -147,7 +149,8 @@ class _QueryBuilderState DiagnosticsProperty>('queryKey', widget.queryKey), ); properties.add( - DiagnosticsProperty('builder', widget.builder), + DiagnosticsProperty>( + 'builder', widget.builder), ); properties.add(DiagnosticsProperty('initial', widget.initial)); properties.add( diff --git a/packages/fl_query/lib/src/widgets/state_notifier_listenable.dart b/packages/fl_query/lib/src/widgets/state_notifier_listenable.dart index bc99653..245d42e 100644 --- a/packages/fl_query/lib/src/widgets/state_notifier_listenable.dart +++ b/packages/fl_query/lib/src/widgets/state_notifier_listenable.dart @@ -26,7 +26,7 @@ class _StateNotifierListenableState void initialize() { removeListener = widget.notifier.addListener((_) { - setState(() {}); + if (mounted) setState(() {}); }); } diff --git a/packages/fl_query_hooks/.flutter-plugins b/packages/fl_query_hooks/.flutter-plugins index eec8974..d9cfa91 100644 --- a/packages/fl_query_hooks/.flutter-plugins +++ b/packages/fl_query_hooks/.flutter-plugins @@ -1,11 +1,11 @@ # This is a generated file; do not edit or check into version control. -connectivity_plus=/home/krtirtho/.pub-cache/hosted/pub.dev/connectivity_plus-2.3.9/ -connectivity_plus_linux=/home/krtirtho/.pub-cache/hosted/pub.dev/connectivity_plus_linux-1.3.1/ -connectivity_plus_macos=/home/krtirtho/.pub-cache/hosted/pub.dev/connectivity_plus_macos-1.2.6/ -connectivity_plus_web=/home/krtirtho/.pub-cache/hosted/pub.dev/connectivity_plus_web-1.2.5/ -connectivity_plus_windows=/home/krtirtho/.pub-cache/hosted/pub.dev/connectivity_plus_windows-1.2.2/ -path_provider=/home/krtirtho/.pub-cache/hosted/pub.dev/path_provider-2.0.12/ -path_provider_android=/home/krtirtho/.pub-cache/hosted/pub.dev/path_provider_android-2.0.22/ -path_provider_foundation=/home/krtirtho/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.1/ -path_provider_linux=/home/krtirtho/.pub-cache/hosted/pub.dev/path_provider_linux-2.1.7/ -path_provider_windows=/home/krtirtho/.pub-cache/hosted/pub.dev/path_provider_windows-2.1.3/ +connectivity_plus=/home/kingkor/.pub-cache/hosted/pub.dev/connectivity_plus-2.3.9/ +connectivity_plus_linux=/home/kingkor/.pub-cache/hosted/pub.dev/connectivity_plus_linux-1.3.1/ +connectivity_plus_macos=/home/kingkor/.pub-cache/hosted/pub.dev/connectivity_plus_macos-1.2.6/ +connectivity_plus_web=/home/kingkor/.pub-cache/hosted/pub.dev/connectivity_plus_web-1.2.5/ +connectivity_plus_windows=/home/kingkor/.pub-cache/hosted/pub.dev/connectivity_plus_windows-1.2.2/ +path_provider=/home/kingkor/.pub-cache/hosted/pub.dev/path_provider-2.0.12/ +path_provider_android=/home/kingkor/.pub-cache/hosted/pub.dev/path_provider_android-2.0.22/ +path_provider_foundation=/home/kingkor/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.1/ +path_provider_linux=/home/kingkor/.pub-cache/hosted/pub.dev/path_provider_linux-2.1.8/ +path_provider_windows=/home/kingkor/.pub-cache/hosted/pub.dev/path_provider_windows-2.1.3/ diff --git a/packages/fl_query_hooks/.flutter-plugins-dependencies b/packages/fl_query_hooks/.flutter-plugins-dependencies index 7aac10d..86c7133 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.dev/connectivity_plus-2.3.9/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/home/krtirtho/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.1/","native_build":true,"dependencies":[]}],"android":[{"name":"connectivity_plus","path":"/home/krtirtho/.pub-cache/hosted/pub.dev/connectivity_plus-2.3.9/","native_build":true,"dependencies":[]},{"name":"path_provider_android","path":"/home/krtirtho/.pub-cache/hosted/pub.dev/path_provider_android-2.0.22/","native_build":true,"dependencies":[]}],"macos":[{"name":"connectivity_plus_macos","path":"/home/krtirtho/.pub-cache/hosted/pub.dev/connectivity_plus_macos-1.2.6/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/home/krtirtho/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.1/","native_build":true,"dependencies":[]}],"linux":[{"name":"connectivity_plus_linux","path":"/home/krtirtho/.pub-cache/hosted/pub.dev/connectivity_plus_linux-1.3.1/","native_build":false,"dependencies":[]},{"name":"path_provider_linux","path":"/home/krtirtho/.pub-cache/hosted/pub.dev/path_provider_linux-2.1.7/","native_build":false,"dependencies":[]}],"windows":[{"name":"connectivity_plus_windows","path":"/home/krtirtho/.pub-cache/hosted/pub.dev/connectivity_plus_windows-1.2.2/","native_build":true,"dependencies":[]},{"name":"path_provider_windows","path":"/home/krtirtho/.pub-cache/hosted/pub.dev/path_provider_windows-2.1.3/","native_build":false,"dependencies":[]}],"web":[{"name":"connectivity_plus_web","path":"/home/krtirtho/.pub-cache/hosted/pub.dev/connectivity_plus_web-1.2.5/","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":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]}],"date_created":"2023-02-14 13:29:34.103321","version":"3.7.0"} \ 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/kingkor/.pub-cache/hosted/pub.dev/connectivity_plus-2.3.9/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/home/kingkor/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.1/","native_build":true,"dependencies":[]}],"android":[{"name":"connectivity_plus","path":"/home/kingkor/.pub-cache/hosted/pub.dev/connectivity_plus-2.3.9/","native_build":true,"dependencies":[]},{"name":"path_provider_android","path":"/home/kingkor/.pub-cache/hosted/pub.dev/path_provider_android-2.0.22/","native_build":true,"dependencies":[]}],"macos":[{"name":"connectivity_plus_macos","path":"/home/kingkor/.pub-cache/hosted/pub.dev/connectivity_plus_macos-1.2.6/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/home/kingkor/.pub-cache/hosted/pub.dev/path_provider_foundation-2.1.1/","native_build":true,"dependencies":[]}],"linux":[{"name":"connectivity_plus_linux","path":"/home/kingkor/.pub-cache/hosted/pub.dev/connectivity_plus_linux-1.3.1/","native_build":false,"dependencies":[]},{"name":"path_provider_linux","path":"/home/kingkor/.pub-cache/hosted/pub.dev/path_provider_linux-2.1.8/","native_build":false,"dependencies":[]}],"windows":[{"name":"connectivity_plus_windows","path":"/home/kingkor/.pub-cache/hosted/pub.dev/connectivity_plus_windows-1.2.2/","native_build":true,"dependencies":[]},{"name":"path_provider_windows","path":"/home/kingkor/.pub-cache/hosted/pub.dev/path_provider_windows-2.1.3/","native_build":false,"dependencies":[]}],"web":[{"name":"connectivity_plus_web","path":"/home/kingkor/.pub-cache/hosted/pub.dev/connectivity_plus_web-1.2.5/","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":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]}],"date_created":"2023-02-21 09:32:49.296117","version":"3.7.3"} \ No newline at end of file