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:
@@ -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/
|
||||
|
||||
@@ -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"}
|
||||
@@ -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<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) {
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
|
||||
@@ -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<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) {
|
||||
return context
|
||||
.dependOnInheritedWidgetOfExactType<QueryClientProvider>()!
|
||||
|
||||
@@ -64,17 +64,20 @@ class InfiniteQueryPage<DataType, ErrorType, PageType> {
|
||||
class InfiniteQueryState<DataType, ErrorType, PageType> {
|
||||
final Set<InfiniteQueryPage<DataType, ErrorType, PageType>> pages;
|
||||
final InfiniteQueryFn<DataType, PageType> queryFn;
|
||||
final InfiniteQueryNextPage<DataType, PageType> nextPage;
|
||||
final InfiniteQueryNextPage<DataType, PageType> _nextPage;
|
||||
|
||||
const InfiniteQueryState({
|
||||
required this.pages,
|
||||
required this.queryFn,
|
||||
required this.nextPage,
|
||||
});
|
||||
required InfiniteQueryNextPage<DataType, PageType> 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<DataType>().toList(),
|
||||
);
|
||||
|
||||
bool get hasNextPage => getNextPage != null;
|
||||
|
||||
InfiniteQueryState<DataType, ErrorType, PageType> copyWith({
|
||||
@@ -85,11 +88,23 @@ class InfiniteQueryState<DataType, ErrorType, PageType> {
|
||||
return InfiniteQueryState<DataType, ErrorType, PageType>(
|
||||
pages: pages ?? this.pages,
|
||||
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>
|
||||
extends StateNotifier<InfiniteQueryState<DataType, ErrorType, PageType>>
|
||||
with Retryer<DataType, ErrorType> {
|
||||
@@ -106,7 +121,9 @@ class InfiniteQuery<DataType, ErrorType, KeyType, PageType>
|
||||
this.retryConfig = DefaultConstants.retryConfig,
|
||||
this.refreshConfig = DefaultConstants.refreshConfig,
|
||||
this.jsonConfig,
|
||||
}) : super(InfiniteQueryState<DataType, ErrorType, PageType>(
|
||||
}) : _dataController = StreamController.broadcast(),
|
||||
_errorController = StreamController.broadcast(),
|
||||
super(InfiniteQueryState<DataType, ErrorType, PageType>(
|
||||
pages: {
|
||||
InfiniteQueryPage<DataType, ErrorType, PageType>(
|
||||
page: initialParam,
|
||||
@@ -119,17 +136,30 @@ class InfiniteQuery<DataType, ErrorType, KeyType, PageType>
|
||||
)) {
|
||||
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<DataType, ErrorType, PageType>(
|
||||
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)
|
||||
@@ -146,12 +176,18 @@ class InfiniteQuery<DataType, ErrorType, KeyType, PageType>
|
||||
|
||||
final _mutex = Mutex();
|
||||
final _box = Hive.lazyBox("cache");
|
||||
final StreamController<PageEvent<DataType, PageType>> _dataController;
|
||||
final StreamController<PageEvent<ErrorType, PageType>> _errorController;
|
||||
|
||||
List<DataType> get pages =>
|
||||
state.pages.map((e) => e.data).whereType<DataType>().toList();
|
||||
List<ErrorType> get errors =>
|
||||
state.pages.map((e) => e.error).whereType<ErrorType>().toList();
|
||||
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 isRefreshingPage => (hasPageData || hasPageError) && _mutex.isLocked;
|
||||
@@ -160,8 +196,8 @@ class InfiniteQuery<DataType, ErrorType, KeyType, PageType>
|
||||
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<DataType, ErrorType, KeyType, PageType>
|
||||
() => state.queryFn(page),
|
||||
config: retryConfig,
|
||||
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(
|
||||
pages: {
|
||||
...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),
|
||||
},
|
||||
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<DataType, ErrorType, PageType>(
|
||||
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<DataType, ErrorType, PageType>(
|
||||
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<DataType, ErrorType, KeyType, PageType>
|
||||
|
||||
Future<DataType?> 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<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
|
||||
RemoveListener addListener(
|
||||
Listener<InfiniteQueryState<DataType, ErrorType, PageType>> listener, {
|
||||
@@ -276,4 +355,13 @@ class InfiniteQuery<DataType, ErrorType, KeyType, PageType>
|
||||
@override
|
||||
operator ==(Object other) =>
|
||||
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>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,10 +35,11 @@ class QueryState<DataType, ErrorType> {
|
||||
QueryState<DataType, ErrorType> copyWith({
|
||||
DataType? data,
|
||||
ErrorType? error,
|
||||
DateTime? updatedAt,
|
||||
QueryFn<DataType>? queryFn,
|
||||
}) {
|
||||
return QueryState<DataType, ErrorType>(
|
||||
updatedAt: DateTime.now(),
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
staleDuration: staleDuration,
|
||||
data: data ?? this.data,
|
||||
error: error ?? this.error,
|
||||
@@ -80,7 +81,7 @@ class Query<DataType, ErrorType, KeyType>
|
||||
_initial = jsonConfig!.fromJson(
|
||||
Map.castFrom<dynamic, dynamic, String, dynamic>(json),
|
||||
);
|
||||
state = state.copyWith(data: _initial);
|
||||
state = state.copyWith(data: _initial, updatedAt: DateTime.now());
|
||||
}
|
||||
}).then((_) {
|
||||
if (hasListeners) {
|
||||
@@ -124,7 +125,10 @@ class Query<DataType, ErrorType, KeyType>
|
||||
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<DataType, ErrorType, KeyType>
|
||||
}
|
||||
},
|
||||
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<DataType, ErrorType, KeyType>
|
||||
|
||||
void updateQueryFn(QueryFn<DataType> 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
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fl_query/src/collections/retry_config.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
mixin Retryer<T, E> {
|
||||
void retryOperation(
|
||||
@@ -19,9 +20,20 @@ mixin Retryer<T, E> {
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ValueKey<KeyType> queryKey;
|
||||
|
||||
final QueryBuilderFn builder;
|
||||
final DataType? initial;
|
||||
|
||||
final RetryConfig retryConfig;
|
||||
@@ -30,6 +29,7 @@ class QueryBuilder<DataType, ErrorType, KeyType> extends StatefulWidget {
|
||||
|
||||
// widget specific
|
||||
final bool enabled;
|
||||
final QueryBuilderFn<DataType, ErrorType, KeyType> builder;
|
||||
|
||||
const QueryBuilder(
|
||||
this.queryKey,
|
||||
@@ -44,7 +44,7 @@ class QueryBuilder<DataType, ErrorType, KeyType> 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<DataType, ErrorType, KeyType>
|
||||
StreamSubscription<ErrorType>? errorSubscription;
|
||||
|
||||
void update(_) {
|
||||
setState(() {});
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
Future<void> initialize() async {
|
||||
@@ -115,6 +115,8 @@ class _QueryBuilderState<DataType, ErrorType, KeyType>
|
||||
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<DataType, ErrorType, KeyType>
|
||||
DiagnosticsProperty<ValueKey<KeyType>>('queryKey', widget.queryKey),
|
||||
);
|
||||
properties.add(
|
||||
DiagnosticsProperty<QueryBuilderFn>('builder', widget.builder),
|
||||
DiagnosticsProperty<QueryBuilderFn<DataType, ErrorType, KeyType>>(
|
||||
'builder', widget.builder),
|
||||
);
|
||||
properties.add(DiagnosticsProperty<DataType>('initial', widget.initial));
|
||||
properties.add(
|
||||
|
||||
@@ -26,7 +26,7 @@ class _StateNotifierListenableState<T>
|
||||
|
||||
void initialize() {
|
||||
removeListener = widget.notifier.addListener((_) {
|
||||
setState(() {});
|
||||
if (mounted) setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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"}
|
||||
Reference in New Issue
Block a user