diff --git a/README.md b/README.md index f6b92d5..818a24c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,123 @@ # FL-Query -Asynchronous data caching, refetching & invalidation library for Flutter. FL-Query lets you manage & distribute your data async data without touching any global state \ No newline at end of file +Asynchronous data caching, refetching & invalidation library for Flutter. FL-Query lets you manage & distribute your data async data without touching any global state + +# Examples +All examples of fl-query can be found in the [packages/example/lib](https://github.com/KRTirtho/fl-query/tree/main/packages/example/lib) directory + +Here's a basic example: +```dart +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return QueryBowlScope( + child: MaterialApp( + title: 'FL-Query Demo', + theme: ThemeData( + useMaterial3: true, + primarySwatch: Colors.blue, + ), + home: const MyHomePage(), + ), + ); + } +} + + +// defining jobs that'll return results + +// this query resolve successfully with expected Data +final successJob = QueryJob( + queryKey: "success", + task: (queryKey, externalData) => Future.delayed(const Duration(seconds: 2), + () => "Welcome ($queryKey) ${Random.secure().nextInt(100)}"), +); + +// this query can fail or can be successful +final failedJob = QueryJob( + queryKey: "failure", + task: (queryKey, externalData) => Random().nextBool() + ? Future.error("[$queryKey] Failed for unknown reason") + : Future.value( + "Success, you'll get slowly ${Random().nextInt(100)}!", + ), +); + +class MyHomePage extends StatefulWidget { + const MyHomePage({Key? key}) : super(key: key); + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("Fl Query Example"), + ), + body: Column( + children: [ + Row( + children: [ + QueryBuilder( + job: successJob, + // if you want to pass any external data or variable to the + // query/task function or just pass null + externalData: null, + builder: (context, query) { + // returning based on the status of the query + if (query.isLoading || query.isRefetching) { + return const CircularProgressIndicator(); + } + return TextButton( + child: Text(query.data!), + onPressed: () async { + // refetching data forcibly + await query.refetch(); + }, + ); + }, + ), + QueryBuilder( + job: failedJob, + externalData: null, + builder: (context, query) { + return Row( + children: [ + if (query.hasError) + Text( + "${query.error}. Retrying: ${query.retryAttempts}", + ), + if (query.hasData) + Text( + "Success after ${query.retryAttempts}. Data: ${query.data}"), + ElevatedButton( + child: Text("Refetch ${query.queryKey}"), + onPressed: () => query.refetch(), + ) + ], + ); + }, + ), + ], + ), + ], + ), + ); + } +} + +``` + +# TODO + +- Invalidate Queries when Window Focus Lost +- Invalidate Queries when Connection Lose based configure network behavior \ No newline at end of file diff --git a/packages/fl_query/lib/query.dart b/packages/fl_query/lib/query.dart index ab75c3b..3620750 100644 --- a/packages/fl_query/lib/query.dart +++ b/packages/fl_query/lib/query.dart @@ -16,6 +16,8 @@ typedef QueryListener = FutureOr Function(T); typedef ListenerUnsubscriber = void Function(); +typedef QueryUpdateFunction = FutureOr Function(T? oldData); + class Query extends ChangeNotifier { // all params final String queryKey; @@ -224,7 +226,7 @@ class Query extends ChangeNotifier { /// /// Every time a new instance of data should be returned because of /// immutability - setQueryData(FutureOr Function(T? data) updateFn) async { + void setQueryData(QueryUpdateFunction updateFn) async { final newData = await updateFn(data); if (data == newData) { // TODO: Better Error handling & Error structure diff --git a/packages/fl_query/lib/query_bowl.dart b/packages/fl_query/lib/query_bowl.dart index 4123e03..8805e38 100644 --- a/packages/fl_query/lib/query_bowl.dart +++ b/packages/fl_query/lib/query_bowl.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:fl_query/models/mutation_job.dart'; import 'package:fl_query/models/query_job.dart'; import 'package:fl_query/mutation.dart'; import 'package:fl_query/query.dart'; @@ -115,6 +114,27 @@ class _QueryBowlScopeState extends State { }); } + int removeQueries(List queryKeys) { + int count = 0; + setState(() { + mutations = Set.from( + queries.whereNot((query) { + final isAboutToRip = queryKeys.contains(query.queryKey); + if (isAboutToRip) count++; + return isAboutToRip; + }), + ); + }); + return count; + } + + void clear() { + setState(() { + queries = Set(); + mutations = Set(); + }); + } + @override Widget build(BuildContext context) { _disposeUpdateListeners(); @@ -122,6 +142,8 @@ class _QueryBowlScopeState extends State { return QueryBowl( addQuery: addQuery, addMutation: addMutation, + removeQueries: removeQueries, + clear: clear, queries: queries, mutations: mutations, staleTime: widget.staleTime, @@ -143,6 +165,10 @@ class QueryBowl extends InheritedWidget { final void Function(Mutation mutation) _addMutation; + final int Function(List) removeQueries; + + final void Function() clear; + const QueryBowl({ required Widget child, required final void Function( @@ -153,6 +179,8 @@ class QueryBowl extends InheritedWidget { required final Set queries, required final Set mutations, required this.staleTime, + required this.removeQueries, + required this.clear, Key? key, }) : _addQuery = addQuery, _queries = queries, @@ -160,6 +188,7 @@ class QueryBowl extends InheritedWidget { _addMutation = addMutation, super(child: child, key: key); + @protected Future fetchQuery( QueryJob options, { required Outside externalData, @@ -197,6 +226,7 @@ class QueryBowl extends InheritedWidget { return await query.fetch(); } + @protected Query addQuery( Query query, { required ValueKey key, @@ -223,6 +253,7 @@ class QueryBowl extends InheritedWidget { return query; } + @protected Mutation addMutation( Mutation mutation, { final MutationListener? onData, @@ -277,10 +308,30 @@ class QueryBowl extends InheritedWidget { ); } - void resetQuery(String queryKey) { - _queries - .firstWhereOrNull((element) => element.queryKey == queryKey) - ?.reset(); + void setQueryData( + String queryKey, QueryUpdateFunction updateCb) { + getQuery(queryKey)?.setQueryData(updateCb); + } + + void resetQueries(List queryKeys) { + for (final query in _queries) { + if (!queryKeys.contains(query.queryKey)) continue; + query.reset(); + } + } + + void invalidateQueries(List queryKeys) { + for (final query in _queries) { + if (!queryKeys.contains(query.queryKey)) continue; + // TODO: Implement Invaldiate Queries + } + } + + Future refetchQueries(List queryKeys) async { + for (final query in _queries) { + if (!queryKeys.contains(query.queryKey)) continue; + await query.refetch(); + } } static QueryBowl of(BuildContext context) =>