using valuekeys for keeping track of mounts in query & mutations
QueryBuilder & MutaionBuilder are now null proof
This commit is contained in:
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -12,6 +12,7 @@ dependencies:
|
||||
collection: ^1.16.0
|
||||
flutter:
|
||||
sdk: flutter
|
||||
uuid: ^3.0.6
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user