feat: add infinite query builder with example

fix(query): updateQueryFn refetch when stale and update state of builders when widget is mounted
This commit is contained in:
Kingkor Roy Tirtho
2023-02-21 12:03:45 +06:00
parent 975f9eafe1
commit efa2c81505
21 changed files with 778 additions and 143 deletions
+10 -10
View File
@@ -1,11 +1,11 @@
# This is a generated file; do not edit or check into version control. # 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=/home/kingkor/.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_linux=/home/kingkor/.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_macos=/home/kingkor/.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_web=/home/kingkor/.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/ connectivity_plus_windows=/home/kingkor/.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=/home/kingkor/.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_android=/home/kingkor/.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_foundation=/home/kingkor/.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_linux=/home/kingkor/.pub-cache/hosted/pub.dev/path_provider_linux-2.1.8/
path_provider_windows=/home/krtirtho/.pub-cache/hosted/pub.dev/path_provider_windows-2.1.3/ path_provider_windows=/home/kingkor/.pub-cache/hosted/pub.dev/path_provider_windows-2.1.3/
@@ -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"} {"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"}
+5 -50
View File
@@ -1,5 +1,4 @@
import 'dart:math'; import 'package:example/router.dart';
import 'package:fl_query/fl_query.dart'; import 'package:fl_query/fl_query.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -18,54 +17,10 @@ class MainApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final value = Random().nextInt(200000); return MaterialApp.router(
return MaterialApp( title: 'FL Query Example',
home: Scaffold( showPerformanceOverlay: true,
floatingActionButton: QueryListenable<String, dynamic, String>( routerConfig: router,
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<String, dynamic, String>(
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),
);
},
),
),
); );
} }
} }
@@ -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<String> 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<String, dynamic> 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<String>();
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{
'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<Product> products;
int total;
int skip;
int limit;
PagedProducts({
this.products = const [],
required this.total,
required this.skip,
required this.limit,
});
PagedProducts.fromJson(Map<String, dynamic> json)
: products = json['products']
?.map<Product>((v) => Product.fromJson(
Map.castFrom<dynamic, dynamic, String, dynamic>(v),
))
.toList() ??
[],
total = json['total'],
skip = json['skip'],
limit = json['limit'];
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{
'products': products.map((v) => v.toJson()).toList(),
'total': total,
'skip': skip,
'limit': limit,
};
return data;
}
}
@@ -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'),
),
],
),
);
}
}
@@ -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<InfiniteQueryPageWidget> createState() =>
_InfiniteQueryPageWidgetState();
}
class _InfiniteQueryPageWidgetState extends State<InfiniteQueryPageWidget> {
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<PagedProducts, ClientException, String, int>(
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(),
],
);
},
),
);
}
}
@@ -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<String, dynamic, String>(
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<String, dynamic, String>(
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"),
);
},
),
);
}
}
+22
View File
@@ -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(),
),
],
);
+34 -2
View File
@@ -167,6 +167,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" 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: hive:
dependency: transitive dependency: transitive
description: description:
@@ -183,6 +191,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" 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: js:
dependency: transitive dependency: transitive
description: description:
@@ -199,6 +223,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "2.0.1"
logging:
dependency: transitive
description:
name: logging
sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@@ -445,5 +477,5 @@ packages:
source: hosted source: hosted
version: "6.2.2" version: "6.2.2"
sdks: sdks:
dart: ">=2.19.0 <4.0.0" dart: ">=2.19.0 <3.0.0"
flutter: ">=3.0.0" flutter: ">=3.3.0"
+2
View File
@@ -10,6 +10,8 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
fl_query: ^0.3.1 fl_query: ^0.3.1
go_router: ^6.0.9
http: ^0.13.5
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
+1
View File
@@ -13,3 +13,4 @@ export 'src/core/query.dart';
export 'src/widgets/query_builder.dart'; export 'src/widgets/query_builder.dart';
export 'src/widgets/query_listenable.dart'; export 'src/widgets/query_listenable.dart';
export 'src/widgets/infinite_query_builder.dart';
@@ -8,9 +8,9 @@ abstract class DefaultConstants {
); );
static const RefreshConfig refreshConfig = RefreshConfig( static const RefreshConfig refreshConfig = RefreshConfig(
staleDuration: Duration(seconds: 10), staleDuration: Duration(minutes: 2, milliseconds: 250),
refreshInterval: Duration.zero, refreshInterval: Duration.zero,
refreshOnMount: true, refreshOnMount: false,
refreshOnQueryFnChange: false, refreshOnQueryFnChange: false,
); );
@@ -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/refresh_config.dart';
import 'package:fl_query/src/collections/retry_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/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/provider.dart';
import 'package:fl_query/src/core/query.dart'; import 'package:fl_query/src/core/query.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -83,6 +84,98 @@ class QueryClient {
return await Future.wait(queries.map((query) => query.refresh())); return await Future.wait(queries.map((query) => query.refresh()));
} }
InfiniteQuery<DataType, ErrorType, KeyType, PageType>
createInfiniteQuery<DataType, ErrorType, KeyType, PageType>(
ValueKey<KeyType> key,
InfiniteQueryFn<DataType, PageType> queryFn, {
required InfiniteQueryNextPage<DataType, PageType> nextPage,
required PageType initialParam,
RetryConfig retryConfig = DefaultConstants.retryConfig,
RefreshConfig refreshConfig = DefaultConstants.refreshConfig,
JsonConfig<DataType>? jsonConfig,
}) {
final query = cache.infiniteQueries
.firstWhere(
(query) => query.key == key,
orElse: () => InfiniteQuery<DataType, ErrorType, KeyType, PageType>(
key,
queryFn,
nextPage: nextPage,
initialParam: initialParam,
retryConfig: retryConfig,
refreshConfig: refreshConfig,
jsonConfig: jsonConfig,
),
)
.cast<DataType, ErrorType, KeyType, PageType>();
query.updateQueryFn(queryFn);
cache.addInfiniteQuery(query);
return query;
}
Future<DataType?> fetchInfiniteQuery<DataType, ErrorType, KeyType, PageType>(
ValueKey<KeyType> key, InfiniteQueryFn<DataType, PageType> queryFn,
{required InfiniteQueryNextPage<DataType, PageType> nextPage,
required PageType initialParam,
RetryConfig retryConfig = DefaultConstants.retryConfig,
RefreshConfig refreshConfig = DefaultConstants.refreshConfig,
JsonConfig<DataType>? jsonConfig}) async {
final query = createInfiniteQuery<DataType, ErrorType, KeyType, PageType>(
key,
queryFn,
nextPage: nextPage,
initialParam: initialParam,
retryConfig: retryConfig,
refreshConfig: refreshConfig,
jsonConfig: jsonConfig,
);
return await query.fetch();
}
InfiniteQuery<DataType, ErrorType, KeyType, PageType>?
getInfiniteQuery<DataType, ErrorType, KeyType, PageType>(
ValueKey<KeyType> key) {
return cache.infiniteQueries
.firstWhereOrNull((query) => query.key == key)
?.cast<DataType, ErrorType, KeyType, PageType>();
}
List<InfiniteQuery> getInfiniteQueries(List<ValueKey> keys) {
return cache.infiniteQueries
.where((query) => keys.contains(query.key))
.toList();
}
Future<DataType?>
refreshInfiniteQuery<DataType, ErrorType, KeyType, PageType>(
ValueKey<KeyType> key,
[PageType? page]) async {
final query = getInfiniteQuery<DataType, ErrorType, KeyType, PageType>(key);
if (query == null) return null;
return await query.refresh(page);
}
Future<List<DataType>?>
refreshInfiniteQueryAllPages<DataType, ErrorType, KeyType, PageType>(
ValueKey<KeyType> key) async {
final query = getInfiniteQuery<DataType, ErrorType, KeyType, PageType>(key);
if (query == null) return [];
return await query.refreshAll();
}
Future<List> refreshInfiniteQueries(List<ValueKey> keys) async {
final queries = getInfiniteQueries(keys);
return await Future.wait(queries.map((query) => query.refresh()));
}
Future<Map<ValueKey, List?>> refreshInfiniteQueriesAllPages(
List<ValueKey> 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) { static QueryClient of(BuildContext context) {
return context return context
.dependOnInheritedWidgetOfExactType<QueryClientProvider>()! .dependOnInheritedWidgetOfExactType<QueryClientProvider>()!
@@ -64,17 +64,20 @@ class InfiniteQueryPage<DataType, ErrorType, PageType> {
class InfiniteQueryState<DataType, ErrorType, PageType> { class InfiniteQueryState<DataType, ErrorType, PageType> {
final Set<InfiniteQueryPage<DataType, ErrorType, PageType>> pages; final Set<InfiniteQueryPage<DataType, ErrorType, PageType>> pages;
final InfiniteQueryFn<DataType, PageType> queryFn; final InfiniteQueryFn<DataType, PageType> queryFn;
final InfiniteQueryNextPage<DataType, PageType> nextPage; final InfiniteQueryNextPage<DataType, PageType> _nextPage;
const InfiniteQueryState({ const InfiniteQueryState({
required this.pages, required this.pages,
required this.queryFn, required this.queryFn,
required this.nextPage, required InfiniteQueryNextPage<DataType, PageType> nextPage,
}); }) : _nextPage = nextPage;
PageType get lastPage => pages.last.page; PageType get lastPage => pages.last.page;
PageType? get getNextPage => PageType? get getNextPage => _nextPage(
nextPage(lastPage, pages.map((e) => e.data!).toList()); lastPage,
pages.map((e) => e.data).whereType<DataType>().toList(),
);
bool get hasNextPage => getNextPage != null; bool get hasNextPage => getNextPage != null;
InfiniteQueryState<DataType, ErrorType, PageType> copyWith({ InfiniteQueryState<DataType, ErrorType, PageType> copyWith({
@@ -85,11 +88,23 @@ class InfiniteQueryState<DataType, ErrorType, PageType> {
return InfiniteQueryState<DataType, ErrorType, PageType>( return InfiniteQueryState<DataType, ErrorType, PageType>(
pages: pages ?? this.pages, pages: pages ?? this.pages,
queryFn: queryFn ?? this.queryFn, queryFn: queryFn ?? this.queryFn,
nextPage: nextPage ?? this.nextPage, nextPage: nextPage ?? this._nextPage,
); );
} }
} }
class PageEvent<T, P> {
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<DataType, ErrorType, KeyType, PageType> class InfiniteQuery<DataType, ErrorType, KeyType, PageType>
extends StateNotifier<InfiniteQueryState<DataType, ErrorType, PageType>> extends StateNotifier<InfiniteQueryState<DataType, ErrorType, PageType>>
with Retryer<DataType, ErrorType> { with Retryer<DataType, ErrorType> {
@@ -106,7 +121,9 @@ class InfiniteQuery<DataType, ErrorType, KeyType, PageType>
this.retryConfig = DefaultConstants.retryConfig, this.retryConfig = DefaultConstants.retryConfig,
this.refreshConfig = DefaultConstants.refreshConfig, this.refreshConfig = DefaultConstants.refreshConfig,
this.jsonConfig, this.jsonConfig,
}) : super(InfiniteQueryState<DataType, ErrorType, PageType>( }) : _dataController = StreamController.broadcast(),
_errorController = StreamController.broadcast(),
super(InfiniteQueryState<DataType, ErrorType, PageType>(
pages: { pages: {
InfiniteQueryPage<DataType, ErrorType, PageType>( InfiniteQueryPage<DataType, ErrorType, PageType>(
page: initialParam, page: initialParam,
@@ -119,17 +136,30 @@ class InfiniteQuery<DataType, ErrorType, KeyType, PageType>
)) { )) {
if (jsonConfig != null) { if (jsonConfig != null) {
_mutex.protect(() async { _mutex.protect(() async {
final json = await _box.get(key.toString()); final Map? json = await _box.get(key.value);
if (json != null) { if (json != null) {
state = state.copyWith( state = state.copyWith(
pages: json.map( pages: json.entries
(key, value) => MapEntry( .map(
key as PageType, (entry) => InfiniteQueryPage<DataType, ErrorType, PageType>(
jsonConfig!.fromJson(value), page: entry.key as PageType,
), data: jsonConfig!.fromJson(
), Map.castFrom<dynamic, dynamic, String, dynamic>(
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) if (refreshConfig.refreshInterval > Duration.zero)
@@ -146,12 +176,18 @@ class InfiniteQuery<DataType, ErrorType, KeyType, PageType>
final _mutex = Mutex(); final _mutex = Mutex();
final _box = Hive.lazyBox("cache"); final _box = Hive.lazyBox("cache");
final StreamController<PageEvent<DataType, PageType>> _dataController;
final StreamController<PageEvent<ErrorType, PageType>> _errorController;
List<DataType> get pages => List<DataType> get pages =>
state.pages.map((e) => e.data).whereType<DataType>().toList(); state.pages.map((e) => e.data).whereType<DataType>().toList();
List<ErrorType> get errors => List<ErrorType> get errors =>
state.pages.map((e) => e.error).whereType<ErrorType>().toList(); state.pages.map((e) => e.error).whereType<ErrorType>().toList();
PageType get lastPage => state.lastPage; PageType get lastPage => state.lastPage;
Stream<PageEvent<DataType, PageType>> get dataStream =>
_dataController.stream;
Stream<PageEvent<ErrorType, PageType>> get errorStream =>
_errorController.stream;
bool get isLoadingPage => !hasPageData && !hasPageError && _mutex.isLocked; bool get isLoadingPage => !hasPageData && !hasPageError && _mutex.isLocked;
bool get isRefreshingPage => (hasPageData || hasPageError) && _mutex.isLocked; bool get isRefreshingPage => (hasPageData || hasPageError) && _mutex.isLocked;
@@ -160,8 +196,8 @@ class InfiniteQuery<DataType, ErrorType, KeyType, PageType>
bool get hasPages => pages.isNotEmpty; bool get hasPages => pages.isNotEmpty;
bool get hasErrors => errors.isNotEmpty; bool get hasErrors => errors.isNotEmpty;
bool get hasPageData => state.pages.last.data != null; bool get hasPageData => !hasPages ? false : state.pages.last.data != null;
bool get hasPageError => state.pages.last.error != null; bool get hasPageError => !hasPages ? false : state.pages.last.error != null;
bool get hasNextPage => state.hasNextPage; bool get hasNextPage => state.hasNextPage;
@@ -171,51 +207,54 @@ class InfiniteQuery<DataType, ErrorType, KeyType, PageType>
() => state.queryFn(page), () => state.queryFn(page),
config: retryConfig, config: retryConfig,
onSuccessful: (data) async { onSuccessful: (data) async {
final dataPage = state.pages
.firstWhere(
(e) => e.page == page,
orElse: () => InfiniteQueryPage<DataType, ErrorType, PageType>(
page: page,
updatedAt: DateTime.now(),
staleDuration: refreshConfig.staleDuration,
),
)
.copyWith(data: data);
state = state.copyWith( state = state.copyWith(
pages: { pages: {...state.pages..remove(dataPage), dataPage},
...state.pages,
state.pages
.firstWhere(
(e) => e.page == page,
orElse: () =>
InfiniteQueryPage<DataType, ErrorType, PageType>(
page: page,
updatedAt: DateTime.now(),
staleDuration: refreshConfig.staleDuration,
),
)
.copyWith(data: data),
},
); );
if (dataPage.data != null)
_dataController.add(PageEvent.fromPage(dataPage));
if (jsonConfig != null) { if (jsonConfig != null) {
await _box.put( await _box.put(
key.toString(), key.value,
state.pages.map( Map.fromEntries(
(e) => MapEntry( state.pages.map(
e.page, (e) => MapEntry(
e.data != null ? jsonConfig!.toJson(e.data!) : null, e.page,
e.data != null ? jsonConfig!.toJson(e.data!) : null,
),
), ),
), ),
); );
} }
}, },
onFailed: (error) { onFailed: (error) {
final errorPage = state.pages
.firstWhere(
(e) => e.page == page,
orElse: () => InfiniteQueryPage<DataType, ErrorType, PageType>(
page: page,
updatedAt: DateTime.now(),
staleDuration: refreshConfig.staleDuration,
),
)
.copyWith(error: error);
state = state.copyWith( state = state.copyWith(
pages: { pages: {
...state.pages, ...state.pages..remove(errorPage),
state.pages errorPage,
.firstWhere(
(e) => e.page == page,
orElse: () =>
InfiniteQueryPage<DataType, ErrorType, PageType>(
page: page,
updatedAt: DateTime.now(),
staleDuration: refreshConfig.staleDuration,
),
)
.copyWith(error: error),
}, },
); );
if (errorPage.error != null)
_errorController.add(PageEvent.fromPage(errorPage));
}, },
); );
}); });
@@ -246,14 +285,54 @@ class InfiniteQuery<DataType, ErrorType, KeyType, PageType>
Future<DataType?> fetchNext() async { Future<DataType?> fetchNext() async {
final nextPage = state.getNextPage; final nextPage = state.getNextPage;
if (_mutex.isLocked || nextPage != null) { if (_mutex.isLocked || nextPage == null) {
return state.pages.firstWhereOrNull((e) => e.page == nextPage)?.data; return state.pages.lastOrNull?.data;
} }
return await _operation(nextPage!).then((_) { return await _operation(nextPage).then((_) {
return state.pages.firstWhereOrNull((e) => e.page == nextPage)?.data; return state.pages.firstWhereOrNull((e) => e.page == nextPage)?.data;
}); });
} }
void updateQueryFn(InfiniteQueryFn<DataType, PageType> 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<DataType, PageType> nextPage) {
state = state.copyWith(nextPage: nextPage);
}
void setPageData(PageType page, DataType data) {
final newPage = state.pages
.firstWhere(
(e) => e.page == page,
orElse: () => InfiniteQueryPage<DataType, ErrorType, PageType>(
page: page,
updatedAt: DateTime.now(),
staleDuration: refreshConfig.staleDuration,
),
)
.copyWith(data: data);
state = state.copyWith(
pages: {
...state.pages..remove(newPage),
newPage,
},
);
}
@override @override
RemoveListener addListener( RemoveListener addListener(
Listener<InfiniteQueryState<DataType, ErrorType, PageType>> listener, { Listener<InfiniteQueryState<DataType, ErrorType, PageType>> listener, {
@@ -276,4 +355,13 @@ class InfiniteQuery<DataType, ErrorType, KeyType, PageType>
@override @override
operator ==(Object other) => operator ==(Object other) =>
identical(this, other) || other is InfiniteQuery && key == other.key; identical(this, other) || other is InfiniteQuery && key == other.key;
@override
int get hashCode => key.hashCode;
InfiniteQuery<NewDataType, NewErrorType, NewKeyType, NewPageType>
cast<NewDataType, NewErrorType, NewKeyType, NewPageType>() {
return this
as InfiniteQuery<NewDataType, NewErrorType, NewKeyType, NewPageType>;
}
} }
+10 -9
View File
@@ -35,10 +35,11 @@ class QueryState<DataType, ErrorType> {
QueryState<DataType, ErrorType> copyWith({ QueryState<DataType, ErrorType> copyWith({
DataType? data, DataType? data,
ErrorType? error, ErrorType? error,
DateTime? updatedAt,
QueryFn<DataType>? queryFn, QueryFn<DataType>? queryFn,
}) { }) {
return QueryState<DataType, ErrorType>( return QueryState<DataType, ErrorType>(
updatedAt: DateTime.now(), updatedAt: updatedAt ?? this.updatedAt,
staleDuration: staleDuration, staleDuration: staleDuration,
data: data ?? this.data, data: data ?? this.data,
error: error ?? this.error, error: error ?? this.error,
@@ -80,7 +81,7 @@ class Query<DataType, ErrorType, KeyType>
_initial = jsonConfig!.fromJson( _initial = jsonConfig!.fromJson(
Map.castFrom<dynamic, dynamic, String, dynamic>(json), Map.castFrom<dynamic, dynamic, String, dynamic>(json),
); );
state = state.copyWith(data: _initial); state = state.copyWith(data: _initial, updatedAt: DateTime.now());
} }
}).then((_) { }).then((_) {
if (hasListeners) { if (hasListeners) {
@@ -124,7 +125,10 @@ class Query<DataType, ErrorType, KeyType>
state.queryFn, state.queryFn,
config: retryConfig, config: retryConfig,
onSuccessful: (DataType? data) { onSuccessful: (DataType? data) {
state = state.copyWith(data: data); state = state.copyWith(
data: data,
updatedAt: DateTime.now(),
);
if (data != null) _dataController.add(data); if (data != null) _dataController.add(data);
if (jsonConfig != null && data != null) { if (jsonConfig != null && data != null) {
_box.put( _box.put(
@@ -134,7 +138,7 @@ class Query<DataType, ErrorType, KeyType>
} }
}, },
onFailed: (ErrorType? error) { onFailed: (ErrorType? error) {
state = state.copyWith(error: error); state = state.copyWith(error: error, updatedAt: DateTime.now());
if (error != null) _errorController.add(error); if (error != null) _errorController.add(error);
}, },
); );
@@ -154,17 +158,14 @@ class Query<DataType, ErrorType, KeyType>
void updateQueryFn(QueryFn<DataType> queryFn) { void updateQueryFn(QueryFn<DataType> queryFn) {
if (state.queryFn == queryFn) return; 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); state = state.copyWith(queryFn: queryFn);
if (stale || refreshConfig.refreshOnQueryFnChange) { if (state.isStale || refreshConfig.refreshOnQueryFnChange) {
refresh(); refresh();
} }
} }
void setData(DataType data) { void setData(DataType data) {
state = state.copyWith(data: data); state = state.copyWith(data: data, updatedAt: DateTime.now());
} }
@override @override
+15 -3
View File
@@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:fl_query/src/collections/retry_config.dart'; import 'package:fl_query/src/collections/retry_config.dart';
import 'package:flutter/material.dart';
mixin Retryer<T, E> { mixin Retryer<T, E> {
void retryOperation( void retryOperation(
@@ -19,9 +20,20 @@ mixin Retryer<T, E> {
final result = await completer.future; final result = await completer.future;
onSuccessful(result); onSuccessful(result);
break; break;
} catch (e) { } catch (e, stack) {
if (attempts == config.maxRetries - 1 && e is E?) { if (attempts == config.maxRetries - 1) {
onFailed(e as E?); if (e is E?) {
onFailed(e as E?);
} else {
FlutterError.reportError(
FlutterErrorDetails(
exception: e,
library: 'fl_query',
context: ErrorDescription('retryOperation'),
stack: stack,
),
);
}
} }
} }
} }
@@ -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<DataType, ErrorType, KeyType, PageType> = Widget
Function(
BuildContext context,
InfiniteQuery<DataType, ErrorType, KeyType, PageType> query,
);
class InfiniteQueryBuilder<DataType, ErrorType, KeyType, PageType>
extends StatefulWidget {
final InfiniteQueryFn<DataType, PageType> queryFn;
final ValueKey<KeyType> queryKey;
final PageType initialPage;
final InfiniteQueryNextPage<DataType, PageType> nextPage;
final RetryConfig retryConfig;
final RefreshConfig refreshConfig;
final JsonConfig<DataType>? jsonConfig;
final ValueChanged<PageEvent<DataType, PageType>>? onData;
final ValueChanged<PageEvent<ErrorType, PageType>>? onError;
// widget specific
final bool enabled;
final InfiniteQueryBuilderFn<DataType, ErrorType, KeyType, PageType> 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<InfiniteQueryBuilder<DataType, ErrorType, KeyType, PageType>>
createState() =>
_InfiniteQueryBuilderState<DataType, ErrorType, KeyType, PageType>();
}
class _InfiniteQueryBuilderState<DataType, ErrorType, KeyType, PageType>
extends State<
InfiniteQueryBuilder<DataType, ErrorType, KeyType, PageType>> {
InfiniteQuery<DataType, ErrorType, KeyType, PageType>? query;
VoidCallback? removeListener;
StreamSubscription<PageEvent<DataType, PageType>>? dataSubscription;
StreamSubscription<PageEvent<ErrorType, PageType>>? errorSubscription;
void update(_) {
if (mounted) setState(() {});
}
Future<void> 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!);
}
}
@@ -18,7 +18,6 @@ class QueryBuilder<DataType, ErrorType, KeyType> extends StatefulWidget {
final QueryFn<DataType> queryFn; final QueryFn<DataType> queryFn;
final ValueKey<KeyType> queryKey; final ValueKey<KeyType> queryKey;
final QueryBuilderFn builder;
final DataType? initial; final DataType? initial;
final RetryConfig retryConfig; final RetryConfig retryConfig;
@@ -30,6 +29,7 @@ class QueryBuilder<DataType, ErrorType, KeyType> extends StatefulWidget {
// widget specific // widget specific
final bool enabled; final bool enabled;
final QueryBuilderFn<DataType, ErrorType, KeyType> builder;
const QueryBuilder( const QueryBuilder(
this.queryKey, this.queryKey,
@@ -44,7 +44,7 @@ class QueryBuilder<DataType, ErrorType, KeyType> extends StatefulWidget {
this.enabled = true, this.enabled = true,
super.key, super.key,
}) : assert( }) : assert(
enabled && jsonConfig != null, (jsonConfig != null && enabled) || jsonConfig == null,
'jsonConfig is only supported when enabled is true', 'jsonConfig is only supported when enabled is true',
); );
@@ -63,7 +63,7 @@ class _QueryBuilderState<DataType, ErrorType, KeyType>
StreamSubscription<ErrorType>? errorSubscription; StreamSubscription<ErrorType>? errorSubscription;
void update(_) { void update(_) {
setState(() {}); if (mounted) setState(() {});
} }
Future<void> initialize() async { Future<void> initialize() async {
@@ -115,6 +115,8 @@ class _QueryBuilderState<DataType, ErrorType, KeyType>
removeListener?.call(); removeListener?.call();
initialize(); initialize();
return; return;
} else if (oldWidget.enabled != widget.enabled && widget.enabled) {
query!.fetch();
} }
if (oldWidget.queryFn != widget.queryFn) { if (oldWidget.queryFn != widget.queryFn) {
query!.updateQueryFn(widget.queryFn); query!.updateQueryFn(widget.queryFn);
@@ -147,7 +149,8 @@ class _QueryBuilderState<DataType, ErrorType, KeyType>
DiagnosticsProperty<ValueKey<KeyType>>('queryKey', widget.queryKey), DiagnosticsProperty<ValueKey<KeyType>>('queryKey', widget.queryKey),
); );
properties.add( properties.add(
DiagnosticsProperty<QueryBuilderFn>('builder', widget.builder), DiagnosticsProperty<QueryBuilderFn<DataType, ErrorType, KeyType>>(
'builder', widget.builder),
); );
properties.add(DiagnosticsProperty<DataType>('initial', widget.initial)); properties.add(DiagnosticsProperty<DataType>('initial', widget.initial));
properties.add( properties.add(
@@ -26,7 +26,7 @@ class _StateNotifierListenableState<T>
void initialize() { void initialize() {
removeListener = widget.notifier.addListener((_) { removeListener = widget.notifier.addListener((_) {
setState(() {}); if (mounted) setState(() {});
}); });
} }
+10 -10
View File
@@ -1,11 +1,11 @@
# This is a generated file; do not edit or check into version control. # 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=/home/kingkor/.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_linux=/home/kingkor/.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_macos=/home/kingkor/.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_web=/home/kingkor/.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/ connectivity_plus_windows=/home/kingkor/.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=/home/kingkor/.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_android=/home/kingkor/.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_foundation=/home/kingkor/.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_linux=/home/kingkor/.pub-cache/hosted/pub.dev/path_provider_linux-2.1.8/
path_provider_windows=/home/krtirtho/.pub-cache/hosted/pub.dev/path_provider_windows-2.1.3/ path_provider_windows=/home/kingkor/.pub-cache/hosted/pub.dev/path_provider_windows-2.1.3/
@@ -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"} {"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"}