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:
@@ -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"),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user