diff --git a/README.md b/README.md new file mode 100644 index 0000000..f6b92d5 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# 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 diff --git a/packages/example/pubspec.lock b/packages/example/pubspec.lock index bba5262..26e2b8a 100644 --- a/packages/example/pubspec.lock +++ b/packages/example/pubspec.lock @@ -43,6 +43,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.16.0" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" cupertino_icons: dependency: "direct main" description: @@ -184,6 +191,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.1" + uuid: + dependency: transitive + description: + name: uuid + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.6" vector_math: dependency: transitive description: diff --git a/packages/fl_query/lib/mutation.dart b/packages/fl_query/lib/mutation.dart index 16d7264..a08ec71 100644 --- a/packages/fl_query/lib/mutation.dart +++ b/packages/fl_query/lib/mutation.dart @@ -34,7 +34,7 @@ class Mutation extends ChangeNotifier { /// used for keeping track of mutation activity. If the are no mounts & /// the passed cached time is over than the mutation is removed from /// storage/cache - Set _mounts = {}; + Set> _mounts = {}; @protected final Set> onDataListeners = {}; @@ -87,20 +87,20 @@ class Mutation extends ChangeNotifier { bool get isInactive => _mounts.isEmpty; // all methods - void mount(Widget widget) { - _mounts.add(widget); + void mount(ValueKey uKey) { + _mounts.add(uKey); } - void unmount(Widget widget) { + void unmount(ValueKey uKey) { if (_mounts.length == 1) { Future.delayed(_cacheTime, () { - _mounts.remove(widget); + _mounts.remove(uKey); // for letting know QueryBowl that this one's time has come for // getting crushed notifyListeners(); }); } else { - _mounts.remove(widget); + _mounts.remove(uKey); } } diff --git a/packages/fl_query/lib/mutation_builder.dart b/packages/fl_query/lib/mutation_builder.dart index 367f724..ac3ab6a 100644 --- a/packages/fl_query/lib/mutation_builder.dart +++ b/packages/fl_query/lib/mutation_builder.dart @@ -1,6 +1,7 @@ import 'package:fl_query/models/mutation_job.dart'; import 'package:fl_query/mutation.dart'; import 'package:fl_query/query_bowl.dart'; +import 'package:fl_query/utils.dart'; import 'package:flutter/widgets.dart'; class MutationBuilder extends StatefulWidget { @@ -36,38 +37,62 @@ class _MutationBuilderState extends State> { late QueryBowl queryBowl; + late ValueKey uKey; + + late Mutation mutation; + @override void initState() { super.initState(); + uKey = ValueKey(uuid.v4()); + mutation = Mutation.fromOptions(widget.job); WidgetsBinding.instance.addPostFrameCallback((_) { queryBowl = QueryBowl.of(context); - queryBowl.addMutation( - widget.job, + mutation = queryBowl.addMutation( + mutation, onData: widget.onData, onError: widget.onError, onMutate: widget.onMutate, - mount: widget, + key: uKey, ); }); } + @override + void didUpdateWidget(covariant MutationBuilder oldWidget) { + if (oldWidget.onData != widget.onData && oldWidget.onData != null) { + mutation.onDataListeners.remove(oldWidget.onData); + if (widget.onData != null) mutation.onDataListeners.add(widget.onData!); + } + if (oldWidget.onError != widget.onError && oldWidget.onError != null) { + mutation.onErrorListeners.remove(oldWidget.onError); + if (widget.onError != null) + mutation.onErrorListeners.add(widget.onError!); + } + if (oldWidget.onMutate != widget.onMutate && oldWidget.onMutate != null) { + mutation.onMutateListeners.remove(oldWidget.onMutate); + if (widget.onMutate != null) + mutation.onMutateListeners.add(widget.onMutate!); + } + super.didUpdateWidget(oldWidget); + } + @override void dispose() { - final mutation = queryBowl.getMutation(widget.job.mutationKey); - mutation?.unmount(widget); - if (widget.onData != null) mutation?.onDataListeners.remove(widget.onData); + mutation.unmount(uKey); + if (widget.onData != null) mutation.onDataListeners.remove(widget.onData); if (widget.onError != null) - mutation?.onErrorListeners.remove(widget.onError); + mutation.onErrorListeners.remove(widget.onError); if (widget.onMutate != null) - mutation?.onMutateListeners.remove(widget.onMutate); + mutation.onMutateListeners.remove(widget.onMutate); super.dispose(); } @override Widget build(BuildContext context) { queryBowl = QueryBowl.of(context); - final mutation = queryBowl.getMutation(widget.job.mutationKey); - if (mutation == null) return Container(); - return widget.builder(context, mutation); + final latestMutation = + queryBowl.getMutation(mutation.mutationKey) ?? mutation; + return widget.builder(context, latestMutation); } } diff --git a/packages/fl_query/lib/query.dart b/packages/fl_query/lib/query.dart index 59446a4..ab75c3b 100644 --- a/packages/fl_query/lib/query.dart +++ b/packages/fl_query/lib/query.dart @@ -61,7 +61,7 @@ class Query extends ChangeNotifier { /// used for keeping track of query activity. If the are no mounts & /// the passed cached time is over than the query is removed from /// storage/cache - Set _mounts = {}; + Set> _mounts = {}; Query({ required this.queryKey, @@ -123,20 +123,20 @@ class Query extends ChangeNotifier { // all methods - void mount(Widget widget) { - _mounts.add(widget); + void mount(ValueKey uKey) { + _mounts.add(uKey); } - void unmount(Widget widget) { + void unmount(ValueKey uKey) { if (_mounts.length == 1) { Future.delayed(_cacheTime, () { - _mounts.remove(widget); + _mounts.remove(uKey); // for letting know QueryBowl that this one's time has come for // getting crushed notifyListeners(); }); } else { - _mounts.remove(widget); + _mounts.remove(uKey); } } diff --git a/packages/fl_query/lib/query_bowl.dart b/packages/fl_query/lib/query_bowl.dart index 0708b7b..4123e03 100644 --- a/packages/fl_query/lib/query_bowl.dart +++ b/packages/fl_query/lib/query_bowl.dart @@ -165,7 +165,7 @@ class QueryBowl extends InheritedWidget { required Outside externalData, final QueryListener? onData, final QueryListener? onError, - Widget? mount, + required ValueKey key, }) async { final prevQuery = _queries.firstWhereOrNull((q) => q.queryKey == options.queryKey); @@ -174,7 +174,7 @@ class QueryBowl extends InheritedWidget { // changed final hasExternalDataChanged = prevQuery.prevUsedExternalData != externalData; - if (mount != null) prevQuery.mount(mount); + prevQuery.mount(key); if (onData != null) prevQuery.onDataListeners.add(onData); if (onError != null) prevQuery.onErrorListeners.add(onError); if (!prevQuery.hasData || hasExternalDataChanged) { @@ -192,34 +192,56 @@ class QueryBowl extends InheritedWidget { onData: onData, onError: onError, ); - if (mount != null) query.mount(mount); + query.mount(key); _addQuery(query); return await query.fetch(); } - void addMutation( - MutationJob options, { + Query addQuery( + Query query, { + required ValueKey key, + final QueryListener? onData, + final QueryListener? onError, + }) { + final prevQuery = + _queries.firstWhereOrNull((q) => q.queryKey == query.queryKey); + if (prevQuery is Query) { + // run the query if its still not called or if externalData has + // changed + if (prevQuery.prevUsedExternalData != query.externalData) + prevQuery.setExternalData(query.externalData); + prevQuery.mount(key); + if (onData != null) prevQuery.onDataListeners.add(onData); + if (onError != null) prevQuery.onErrorListeners.add(onError); + // mounting the widget that is using the query in the prevQuery + return prevQuery; + } + if (onData != null) query.onDataListeners.add(onData); + if (onError != null) query.onErrorListeners.add(onError); + query.mount(key); + _addQuery(query); + return query; + } + + Mutation addMutation( + Mutation mutation, { final MutationListener? onData, final MutationListener? onError, final MutationListener? onMutate, - Widget? mount, + required ValueKey key, }) { final prevMutation = _mutations.firstWhereOrNull( - (mutation) => mutation.mutationKey == options.mutationKey); + (prevMutation) => prevMutation.mutationKey == mutation.mutationKey); if (prevMutation != null && prevMutation is Mutation) { if (onData != null) prevMutation.onDataListeners.add(onData); if (onError != null) prevMutation.onErrorListeners.add(onError); if (onMutate != null) prevMutation.onMutateListeners.add(onMutate); - if (mount != null) prevMutation.mount(mount); + prevMutation.mount(key); + return prevMutation; } else { - final mutation = Mutation.fromOptions( - options, - onData: onData, - onError: onError, - onMutate: onMutate, - ); - if (mount != null) mutation.mount(mount); + mutation.mount(key); _addMutation(mutation); + return mutation; } } diff --git a/packages/fl_query/lib/query_builder.dart b/packages/fl_query/lib/query_builder.dart index 6a69dae..b18c4b0 100644 --- a/packages/fl_query/lib/query_builder.dart +++ b/packages/fl_query/lib/query_builder.dart @@ -1,6 +1,7 @@ import 'package:fl_query/models/query_job.dart'; import 'package:fl_query/query.dart'; import 'package:fl_query/query_bowl.dart'; +import 'package:fl_query/utils.dart'; import 'package:flutter/widgets.dart'; class QueryBuilder extends StatefulWidget { @@ -32,54 +33,63 @@ class QueryBuilder extends StatefulWidget { class _QueryBuilderState extends State> { late QueryBowl queryBowl; + late final ValueKey uKey; + late Query query; @override void initState() { super.initState(); + uKey = ValueKey(uuid.v4()); + query = Query.fromOptions( + widget.job, + externalData: widget.externalData, + ); WidgetsBinding.instance.addPostFrameCallback((_) async { - queryBowl = QueryBowl.of(context); - await queryBowl.fetchQuery( - widget.job, - externalData: widget.externalData, + query = QueryBowl.of(context).addQuery( + query, + key: uKey, onData: widget.onData, onError: widget.onError, - mount: widget, ); + await query.fetch(); }); } @override void didUpdateWidget(covariant oldWidget) { if (oldWidget.externalData != widget.externalData) { - queryBowl = QueryBowl.of(context); - // clearing up the old widget before adding the new updated one - // so unmounted zombie widgets don't get piled up - queryBowl.getQuery(widget.job.queryKey)?.unmount(oldWidget); - queryBowl.fetchQuery( + QueryBowl.of(context).fetchQuery( widget.job, externalData: widget.externalData, onData: widget.onData, onError: widget.onError, - mount: widget, + key: uKey, ); + } else { + if (oldWidget.onData != widget.onData && oldWidget.onData != null) { + query.onDataListeners.remove(oldWidget.onData); + if (widget.onData != null) query.onDataListeners.add(widget.onData!); + } + if (oldWidget.onError != widget.onError && oldWidget.onError != null) { + query.onErrorListeners.remove(oldWidget.onError); + if (widget.onError != null) query.onErrorListeners.add(widget.onError!); + } } super.didUpdateWidget(oldWidget); } @override void dispose() { - final query = queryBowl.getQuery(widget.job.queryKey); - query?.unmount(widget); - if (widget.onData != null) query?.onDataListeners.remove(widget.onData); - if (widget.onError != null) query?.onErrorListeners.remove(widget.onError); + query.unmount(uKey); + if (widget.onData != null) query.onDataListeners.remove(widget.onData); + if (widget.onError != null) query.onErrorListeners.remove(widget.onError); super.dispose(); } @override Widget build(BuildContext context) { queryBowl = QueryBowl.of(context); - final query = queryBowl.getQuery(widget.job.queryKey); - if (query == null) return Container(); - return widget.builder(context, query); + final latestQuery = queryBowl.getQuery(query.queryKey) ?? query; + return widget.builder(context, latestQuery); } } diff --git a/packages/fl_query/lib/utils.dart b/packages/fl_query/lib/utils.dart index b126c3f..61ed734 100644 --- a/packages/fl_query/lib/utils.dart +++ b/packages/fl_query/lib/utils.dart @@ -1,4 +1,5 @@ import 'package:fl_query/query.dart'; +import 'package:uuid/uuid.dart'; Future callQueryListeners(Set> listeners, T data) { return Future.wait(listeners.map( @@ -8,3 +9,5 @@ Future callQueryListeners(Set> listeners, T data) { // await listener(data); // } } + +const uuid = Uuid(); diff --git a/packages/fl_query/pubspec.yaml b/packages/fl_query/pubspec.yaml index 182f522..e763929 100644 --- a/packages/fl_query/pubspec.yaml +++ b/packages/fl_query/pubspec.yaml @@ -12,6 +12,7 @@ dependencies: collection: ^1.16.0 flutter: sdk: flutter + uuid: ^3.0.6 dev_dependencies: flutter_test: