feat: useQuery hook with working example

This commit is contained in:
Kingkor Roy Tirtho
2023-02-23 22:50:37 +06:00
parent 5dde200c84
commit 7fabf44756
142 changed files with 5455 additions and 9 deletions
@@ -0,0 +1,29 @@
import 'package:fl_query_hooks_example/router.dart';
import 'package:fl_query/fl_query.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await QueryClient.initialize(cachePrefix: 'fl_query_example');
runApp(
QueryClientProvider(
child: const MainApp(),
),
);
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
theme: ThemeData(
colorSchemeSeed: Colors.red[100],
useMaterial3: true,
),
title: 'FL Query Hooks Example',
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,31 @@
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'),
),
ListTile(
title: const Text('Mutation'),
onTap: () => GoRouter.of(context).push('/mutation'),
),
],
),
);
}
}
@@ -0,0 +1,105 @@
import 'dart:convert';
import 'package:fl_query_hooks_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("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'),
),
floatingActionButton:
InfiniteQueryListenable("products", builder: (context, query) {
return FloatingActionButton(
onPressed: () {
query?.fetchNext();
},
child: Text(query?.pages.length.toString() ?? "-69"),
);
}),
body: InfiniteQueryBuilder<PagedProducts, ClientException, int>(
"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,128 @@
import 'package:fl_query/fl_query.dart';
import 'package:flutter/material.dart';
class MutationPage extends StatefulWidget {
const MutationPage({super.key});
@override
State<MutationPage> createState() => _MutationPageState();
}
class _MutationPageState extends State<MutationPage> {
late TextEditingController _nameController;
late TextEditingController _emailController;
late TextEditingController _passwordController;
@override
void initState() {
super.initState();
_nameController = TextEditingController();
_emailController = TextEditingController();
_passwordController = TextEditingController();
}
@override
void dispose() {
_nameController.dispose();
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Mutation'),
),
body: MutationBuilder<Map<String, dynamic>, dynamic, Map<String, dynamic>,
dynamic>(
'sign-up',
(variables) {
return Future.delayed(
const Duration(seconds: 1),
() => {
'name': variables['name'],
'email': variables['email'],
'password': variables['password'],
},
);
},
onMutate: (variables) {
print('onMutate: $variables');
return "Recover ME";
},
onData: (data, recoveryData) {
print('onData: $data');
print('recoveryData: $recoveryData');
},
refreshQueries: const ['hello'],
builder: (context, mutation) {
if (mutation.hasData) {
return ListView(
padding: const EdgeInsets.all(16),
children: [
Center(
child: Text('Welcome ${mutation.data!['name']}'),
),
const SizedBox(height: 16),
Center(
child: Text('Your email is ${mutation.data!['email']}'),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
mutation.reset();
},
child: const Text('Log out'),
),
],
);
}
return ListView(
padding: const EdgeInsets.all(16),
children: [
TextField(
controller: _nameController,
keyboardType: TextInputType.name,
decoration: const InputDecoration(
labelText: 'Name',
),
),
const SizedBox(height: 16),
TextField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Email',
),
),
const SizedBox(height: 16),
TextField(
controller: _passwordController,
obscureText: true,
decoration: const InputDecoration(
labelText: 'Password',
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () async {
await mutation.mutate({
'name': _nameController.text,
'email': _emailController.text,
'password': _passwordController.text,
});
},
child: mutation.isMutating
? const CircularProgressIndicator()
: const Text('Sign Up'),
),
],
);
},
),
);
}
}
@@ -0,0 +1,62 @@
import 'dart:math';
import 'package:fl_query/fl_query.dart';
import 'package:fl_query_hooks/fl_query_hooks.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
class QueryPage extends HookWidget {
const QueryPage({super.key});
@override
Widget build(BuildContext context) {
final value = Random().nextInt(200000);
final query = useQuery<String, dynamic>(
'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');
},
);
return Scaffold(
appBar: AppBar(
title: const Text('Query'),
),
floatingActionButton:
QueryListenable<String, dynamic>('hello', builder: (context, query) {
if (query == null) {
return const SizedBox();
}
return FloatingActionButton(
onPressed: () {
query.refresh();
},
child: Text(query.data ?? 'No Data'),
);
}),
body: query.isLoading
? const Center(
child: CircularProgressIndicator(),
)
: query.hasError
? Center(
child: Text(query.error.toString()),
)
: Center(
child: Text(query.data ?? "Unfortunately, there's no data"),
),
);
}
}
@@ -0,0 +1,27 @@
import "package:fl_query_hooks_example/pages/home.dart";
import "package:fl_query_hooks_example/pages/infinite_query.dart";
import "package:fl_query_hooks_example/pages/mutation/mutation.dart";
import "package:fl_query_hooks_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(),
),
GoRoute(
path: '/mutation',
builder: (context, state) => const MutationPage(),
),
],
);