From 2a3ac29f64971fe534fa70600e8be4490429304e Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Wed, 14 Sep 2022 13:14:23 +0600 Subject: [PATCH] feat(infinite-query): add `useInfiniteQuery` hook with example --- .../example/.flutter-plugins-dependencies | 2 +- .../components/basic_hook_infinite_query.dart | 72 ++++++++++ packages/fl_query_hooks/example/lib/main.dart | 30 +++-- .../fl_query_hooks/lib/fl_query_hooks.dart | 1 + .../lib/src/use_infinite_query.dart | 123 ++++++++++++++++++ 5 files changed, 218 insertions(+), 10 deletions(-) create mode 100644 packages/fl_query_hooks/example/lib/components/basic_hook_infinite_query.dart create mode 100644 packages/fl_query_hooks/lib/src/use_infinite_query.dart diff --git a/packages/fl_query/example/.flutter-plugins-dependencies b/packages/fl_query/example/.flutter-plugins-dependencies index 061cf3d..450a8d6 100644 --- a/packages/fl_query/example/.flutter-plugins-dependencies +++ b/packages/fl_query/example/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"connectivity_plus","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus-2.3.6/","native_build":true,"dependencies":[]}],"android":[{"name":"connectivity_plus","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus-2.3.6/","native_build":true,"dependencies":[]}],"macos":[{"name":"connectivity_plus_macos","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_macos-1.2.4/","native_build":true,"dependencies":[]}],"linux":[{"name":"connectivity_plus_linux","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_linux-1.3.1/","native_build":false,"dependencies":[]}],"windows":[{"name":"connectivity_plus_windows","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_windows-1.2.2/","native_build":true,"dependencies":[]}],"web":[{"name":"connectivity_plus_web","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_web-1.2.3/","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":[]}],"date_created":"2022-09-14 11:35:53.826188","version":"3.3.0"} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"connectivity_plus","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus-2.3.6/","native_build":true,"dependencies":[]}],"android":[{"name":"connectivity_plus","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus-2.3.6/","native_build":true,"dependencies":[]}],"macos":[{"name":"connectivity_plus_macos","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_macos-1.2.4/","native_build":true,"dependencies":[]}],"linux":[{"name":"connectivity_plus_linux","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_linux-1.3.1/","native_build":false,"dependencies":[]}],"windows":[{"name":"connectivity_plus_windows","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_windows-1.2.2/","native_build":true,"dependencies":[]}],"web":[{"name":"connectivity_plus_web","path":"/home/krtirtho/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_web-1.2.3/","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":[]}],"date_created":"2022-09-14 13:12:02.969795","version":"3.3.0"} \ No newline at end of file diff --git a/packages/fl_query_hooks/example/lib/components/basic_hook_infinite_query.dart b/packages/fl_query_hooks/example/lib/components/basic_hook_infinite_query.dart new file mode 100644 index 0000000..e000ff8 --- /dev/null +++ b/packages/fl_query_hooks/example/lib/components/basic_hook_infinite_query.dart @@ -0,0 +1,72 @@ +import 'dart:convert'; + +import 'package:fl_query_hooks/fl_query_hooks.dart'; +import 'package:flutter/material.dart'; +import 'package:fl_query/fl_query.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:http/http.dart' as http; + +final infiniteQueryHookJob = InfiniteQueryJob( + queryKey: "infinite-posts", + initialParam: 1, + task: (queryKey, pageParam, externalData) async { + return jsonDecode( + (await http.get( + Uri.parse("https://jsonplaceholder.typicode.com/posts/$pageParam"), + )) + .body, + ); + }, + getNextPageParam: (lastPage, lastParam) { + return lastParam + 1; + }, + getPreviousPageParam: (lastPage, lastParam) { + return lastParam - 1; + }, +); + +class BasicHookInfiniteQueryExample extends HookWidget { + const BasicHookInfiniteQueryExample({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final infiniteQuery = useInfiniteQuery( + job: infiniteQueryHookJob, + externalData: null, + ); + + return Scaffold( + appBar: AppBar(), + body: Stack( + children: [ + ListView.builder( + itemCount: infiniteQuery.pages.length, + itemBuilder: (context, index) { + final page = infiniteQuery.pages[index]; + return ListTile( + title: Text(page?["title"] ?? ""), + subtitle: Text(page?["body"] ?? ""), + ); + }, + ), + Align( + alignment: Alignment.bottomRight, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.refresh_rounded), + onPressed: () => infiniteQuery.refetchPages(), + ), + IconButton( + icon: const Icon(Icons.get_app_rounded), + onPressed: () => infiniteQuery.fetchNextPage(), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/packages/fl_query_hooks/example/lib/main.dart b/packages/fl_query_hooks/example/lib/main.dart index d8297aa..069f3a0 100644 --- a/packages/fl_query_hooks/example/lib/main.dart +++ b/packages/fl_query_hooks/example/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:fl_query_hooks_example/components/basic_hook_infinite_query.dart'; import 'package:fl_query_hooks_example/components/basic_hook_mutation.dart'; import 'package:fl_query_hooks_example/components/basic_hook_query.dart'; import 'package:fl_query_hooks_example/components/lazy_hook_query.dart'; @@ -49,15 +50,26 @@ class _MyHomePageState extends State with WidgetsBindingObserver { child: Padding( padding: const EdgeInsets.all(8.0), child: Column( - children: const [ - BasicHookQueryExample(), - QueryHookExternalDataExample(), - LazyHookQueryExample(), - QueryHookVariableKeyExample(), - QueryHookPreviousDataExample(), - Divider(), - BasicHookMutationExample(), - MutationHookVariableKeyExample(), + children: [ + const BasicHookQueryExample(), + const QueryHookExternalDataExample(), + const LazyHookQueryExample(), + const QueryHookVariableKeyExample(), + const QueryHookPreviousDataExample(), + ListTile( + title: const Text("Infinite Query Example"), + trailing: const Icon(Icons.open_in_new), + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const BasicHookInfiniteQueryExample(), + ), + ); + }, + ), + const Divider(), + const BasicHookMutationExample(), + const MutationHookVariableKeyExample(), ], ), )), diff --git a/packages/fl_query_hooks/lib/fl_query_hooks.dart b/packages/fl_query_hooks/lib/fl_query_hooks.dart index e7dec27..0629187 100644 --- a/packages/fl_query_hooks/lib/fl_query_hooks.dart +++ b/packages/fl_query_hooks/lib/fl_query_hooks.dart @@ -2,3 +2,4 @@ library fl_query_hooks; export 'src/use_query.dart'; export 'src/use_mutation.dart'; +export 'src/use_infinite_query.dart'; diff --git a/packages/fl_query_hooks/lib/src/use_infinite_query.dart b/packages/fl_query_hooks/lib/src/use_infinite_query.dart new file mode 100644 index 0000000..59f607a --- /dev/null +++ b/packages/fl_query_hooks/lib/src/use_infinite_query.dart @@ -0,0 +1,123 @@ +// ignore_for_file: invalid_use_of_protected_member + +import 'package:fl_query/fl_query.dart'; +import 'package:fl_query_hooks/src/utils.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; + +InfiniteQuery + useInfiniteQuery({ + required InfiniteQueryJob job, + required Outside externalData, + + // /// Called when the query returns new data, on query + // /// refetch or query gets expired + // QueryListener? onData, + + // /// Called when the query returns error + // QueryListener? onError, + List? keys, +}) { + final context = useContext(); + final QueryBowl queryBowl = QueryBowl.of(context); + final ValueKey uKey = useMemoized(() => ValueKey(uuid.v4()), []); + final query = useRef( + InfiniteQuery.fromOptions( + job, + externalData: externalData, + queryBowl: queryBowl, + ), + ); + + final oldJob = usePrevious(job); + final oldExternalData = usePrevious(externalData); + // final oldOnData = usePrevious(onData); + // final oldOnError = usePrevious(onError); + + final init = useCallback(([T? previousData]) { + query.value = queryBowl.addInfiniteQuery( + job, + externalData: externalData, + // previousData: previousData, + key: uKey, + // onData: onData, + // onError: onError, + ); + final hasExternalDataChanged = query.value.externalData != null && + query.value.prevUsedExternalData != null && + !isShallowEqual( + query.value.externalData!, query.value.prevUsedExternalData!); + if (query.value.fetched && hasExternalDataChanged) { + query.value.refetch(); + } else if (!query.value.fetched) { + query.value.fetch(); + } + }, [ + queryBowl, + query.value, + uKey, + job, + externalData, + // onData, + // onError, + ]); + + final disposeQuery = useCallback(() { + query.value.unmount(uKey); + // if (onData != null) query.value.removeDataListener(onData); + // if (onError != null) query.value.removeErrorListener(onError); + }, [ + query.value, + uKey, + // onData, + // onError, + ]); + + useEffect(() { + init(); + return disposeQuery; + }, []); + + useEffect(() { + // final hasOnErrorChanged = oldOnError != onError && oldOnError != null; + // final hasOnDataChanged = oldOnData != onData && oldOnData != null; + if (oldJob != null && oldJob.queryKey != job.queryKey) { + disposeQuery(); + init(); + } else if (oldExternalData != null && + externalData != null && + !isShallowEqual(oldExternalData, externalData)) { + if (job.refetchOnExternalDataChange ?? + queryBowl.refetchOnExternalDataChange) { + QueryBowl.of(context).addInfiniteQuery( + job, + externalData: externalData, + key: uKey, + // onData: onData, + // onError: onError, + )..refetchPages(); + } else { + QueryBowl.of(context) + .getQuery(job.queryKey) + ?.setExternalData(externalData); + } + + // if (hasOnDataChanged) query.value.removeDataListener(oldOnData); + // if (hasOnErrorChanged) query.value.removeErrorListener(oldOnError); + } + // else { + // if (hasOnDataChanged) { + // query.value.removeDataListener(oldOnData); + // if (onData != null) query.value.addDataListener(onData); + // } + // if (hasOnErrorChanged) { + // query.value.removeErrorListener(oldOnError); + // if (onError != null) query.value.addErrorListener(onError); + // } + // } + return null; + }); + + return queryBowl.getInfiniteQuery(job.queryKey) ?? + query.value; +}