using valuekeys for keeping track of mounts in query & mutations

QueryBuilder & MutaionBuilder are now null proof
This commit is contained in:
Kingkor Roy Tirtho
2022-06-20 12:28:37 +06:00
parent 7d337e02da
commit d7635abb0e
9 changed files with 134 additions and 56 deletions
+3
View File
@@ -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
+14
View File
@@ -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:
+6 -6
View File
@@ -34,7 +34,7 @@ class Mutation<T extends Object, V> 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<Widget> _mounts = {};
Set<ValueKey<String>> _mounts = {};
@protected
final Set<MutationListener<T>> onDataListeners = {};
@@ -87,20 +87,20 @@ class Mutation<T extends Object, V> extends ChangeNotifier {
bool get isInactive => _mounts.isEmpty;
// all methods
void mount(Widget widget) {
_mounts.add(widget);
void mount(ValueKey<String> uKey) {
_mounts.add(uKey);
}
void unmount(Widget widget) {
void unmount(ValueKey<String> 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);
}
}
+36 -11
View File
@@ -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<T extends Object, V> extends StatefulWidget {
@@ -36,38 +37,62 @@ class _MutationBuilderState<T extends Object, V>
extends State<MutationBuilder<T, V>> {
late QueryBowl queryBowl;
late ValueKey<String> uKey;
late Mutation<T, V> mutation;
@override
void initState() {
super.initState();
uKey = ValueKey<String>(uuid.v4());
mutation = Mutation<T, V>.fromOptions(widget.job);
WidgetsBinding.instance.addPostFrameCallback((_) {
queryBowl = QueryBowl.of(context);
queryBowl.addMutation<T, V>(
widget.job,
mutation = queryBowl.addMutation<T, V>(
mutation,
onData: widget.onData,
onError: widget.onError,
onMutate: widget.onMutate,
mount: widget,
key: uKey,
);
});
}
@override
void didUpdateWidget(covariant MutationBuilder<T, V> 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<T, V>(widget.job.mutationKey);
if (mutation == null) return Container();
return widget.builder(context, mutation);
final latestMutation =
queryBowl.getMutation<T, V>(mutation.mutationKey) ?? mutation;
return widget.builder(context, latestMutation);
}
}
+6 -6
View File
@@ -61,7 +61,7 @@ class Query<T extends Object, Outside> 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<Widget> _mounts = {};
Set<ValueKey<String>> _mounts = {};
Query({
required this.queryKey,
@@ -123,20 +123,20 @@ class Query<T extends Object, Outside> extends ChangeNotifier {
// all methods
void mount(Widget widget) {
_mounts.add(widget);
void mount(ValueKey<String> uKey) {
_mounts.add(uKey);
}
void unmount(Widget widget) {
void unmount(ValueKey<String> 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);
}
}
+37 -15
View File
@@ -165,7 +165,7 @@ class QueryBowl extends InheritedWidget {
required Outside externalData,
final QueryListener<T>? onData,
final QueryListener<dynamic>? onError,
Widget? mount,
required ValueKey<String> 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<T, Outside>(query);
return await query.fetch();
}
void addMutation<T extends Object, V>(
MutationJob<T, V> options, {
Query<T, Outside> addQuery<T extends Object, Outside>(
Query<T, Outside> query, {
required ValueKey<String> key,
final QueryListener<T>? onData,
final QueryListener<dynamic>? onError,
}) {
final prevQuery =
_queries.firstWhereOrNull((q) => q.queryKey == query.queryKey);
if (prevQuery is Query<T, Outside>) {
// 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<T, Outside>(query);
return query;
}
Mutation<T, V> addMutation<T extends Object, V>(
Mutation<T, V> mutation, {
final MutationListener<T>? onData,
final MutationListener<dynamic>? onError,
final MutationListener<V>? onMutate,
Widget? mount,
required ValueKey<String> key,
}) {
final prevMutation = _mutations.firstWhereOrNull(
(mutation) => mutation.mutationKey == options.mutationKey);
(prevMutation) => prevMutation.mutationKey == mutation.mutationKey);
if (prevMutation != null && prevMutation is Mutation<T, V>) {
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;
}
}
+28 -18
View File
@@ -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<T extends Object, Outside> extends StatefulWidget {
@@ -32,54 +33,63 @@ class QueryBuilder<T extends Object, Outside> extends StatefulWidget {
class _QueryBuilderState<T extends Object, Outside>
extends State<QueryBuilder<T, Outside>> {
late QueryBowl queryBowl;
late final ValueKey<String> uKey;
late Query<T, Outside> query;
@override
void initState() {
super.initState();
uKey = ValueKey<String>(uuid.v4());
query = Query<T, Outside>.fromOptions(
widget.job,
externalData: widget.externalData,
);
WidgetsBinding.instance.addPostFrameCallback((_) async {
queryBowl = QueryBowl.of(context);
await queryBowl.fetchQuery<T, Outside>(
widget.job,
externalData: widget.externalData,
query = QueryBowl.of(context).addQuery<T, Outside>(
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<T, Outside>(widget.job.queryKey);
if (query == null) return Container();
return widget.builder(context, query);
final latestQuery = queryBowl.getQuery<T, Outside>(query.queryKey) ?? query;
return widget.builder(context, latestQuery);
}
}
+3
View File
@@ -1,4 +1,5 @@
import 'package:fl_query/query.dart';
import 'package:uuid/uuid.dart';
Future<void> callQueryListeners<T>(Set<QueryListener<T>> listeners, T data) {
return Future.wait(listeners.map(
@@ -8,3 +9,5 @@ Future<void> callQueryListeners<T>(Set<QueryListener<T>> listeners, T data) {
// await listener(data);
// }
}
const uuid = Uuid();
+1
View File
@@ -12,6 +12,7 @@ dependencies:
collection: ^1.16.0
flutter:
sdk: flutter
uuid: ^3.0.6
dev_dependencies:
flutter_test: