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"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.16.0"
|
version: "1.16.0"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -184,6 +191,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.1"
|
||||||
|
uuid:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: uuid
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.6"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
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 &
|
/// used for keeping track of mutation activity. If the are no mounts &
|
||||||
/// the passed cached time is over than the mutation is removed from
|
/// the passed cached time is over than the mutation is removed from
|
||||||
/// storage/cache
|
/// storage/cache
|
||||||
Set<Widget> _mounts = {};
|
Set<ValueKey<String>> _mounts = {};
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
final Set<MutationListener<T>> onDataListeners = {};
|
final Set<MutationListener<T>> onDataListeners = {};
|
||||||
@@ -87,20 +87,20 @@ class Mutation<T extends Object, V> extends ChangeNotifier {
|
|||||||
bool get isInactive => _mounts.isEmpty;
|
bool get isInactive => _mounts.isEmpty;
|
||||||
// all methods
|
// all methods
|
||||||
|
|
||||||
void mount(Widget widget) {
|
void mount(ValueKey<String> uKey) {
|
||||||
_mounts.add(widget);
|
_mounts.add(uKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
void unmount(Widget widget) {
|
void unmount(ValueKey<String> uKey) {
|
||||||
if (_mounts.length == 1) {
|
if (_mounts.length == 1) {
|
||||||
Future.delayed(_cacheTime, () {
|
Future.delayed(_cacheTime, () {
|
||||||
_mounts.remove(widget);
|
_mounts.remove(uKey);
|
||||||
// for letting know QueryBowl that this one's time has come for
|
// for letting know QueryBowl that this one's time has come for
|
||||||
// getting crushed
|
// getting crushed
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
_mounts.remove(widget);
|
_mounts.remove(uKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:fl_query/models/mutation_job.dart';
|
import 'package:fl_query/models/mutation_job.dart';
|
||||||
import 'package:fl_query/mutation.dart';
|
import 'package:fl_query/mutation.dart';
|
||||||
import 'package:fl_query/query_bowl.dart';
|
import 'package:fl_query/query_bowl.dart';
|
||||||
|
import 'package:fl_query/utils.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
class MutationBuilder<T extends Object, V> extends StatefulWidget {
|
class MutationBuilder<T extends Object, V> extends StatefulWidget {
|
||||||
@@ -36,38 +37,62 @@ class _MutationBuilderState<T extends Object, V>
|
|||||||
extends State<MutationBuilder<T, V>> {
|
extends State<MutationBuilder<T, V>> {
|
||||||
late QueryBowl queryBowl;
|
late QueryBowl queryBowl;
|
||||||
|
|
||||||
|
late ValueKey<String> uKey;
|
||||||
|
|
||||||
|
late Mutation<T, V> mutation;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
uKey = ValueKey<String>(uuid.v4());
|
||||||
|
mutation = Mutation<T, V>.fromOptions(widget.job);
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
queryBowl = QueryBowl.of(context);
|
queryBowl = QueryBowl.of(context);
|
||||||
queryBowl.addMutation<T, V>(
|
mutation = queryBowl.addMutation<T, V>(
|
||||||
widget.job,
|
mutation,
|
||||||
onData: widget.onData,
|
onData: widget.onData,
|
||||||
onError: widget.onError,
|
onError: widget.onError,
|
||||||
onMutate: widget.onMutate,
|
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
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
final mutation = queryBowl.getMutation(widget.job.mutationKey);
|
mutation.unmount(uKey);
|
||||||
mutation?.unmount(widget);
|
if (widget.onData != null) mutation.onDataListeners.remove(widget.onData);
|
||||||
if (widget.onData != null) mutation?.onDataListeners.remove(widget.onData);
|
|
||||||
if (widget.onError != null)
|
if (widget.onError != null)
|
||||||
mutation?.onErrorListeners.remove(widget.onError);
|
mutation.onErrorListeners.remove(widget.onError);
|
||||||
if (widget.onMutate != null)
|
if (widget.onMutate != null)
|
||||||
mutation?.onMutateListeners.remove(widget.onMutate);
|
mutation.onMutateListeners.remove(widget.onMutate);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
queryBowl = QueryBowl.of(context);
|
queryBowl = QueryBowl.of(context);
|
||||||
final mutation = queryBowl.getMutation<T, V>(widget.job.mutationKey);
|
final latestMutation =
|
||||||
if (mutation == null) return Container();
|
queryBowl.getMutation<T, V>(mutation.mutationKey) ?? mutation;
|
||||||
return widget.builder(context, 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 &
|
/// used for keeping track of query activity. If the are no mounts &
|
||||||
/// the passed cached time is over than the query is removed from
|
/// the passed cached time is over than the query is removed from
|
||||||
/// storage/cache
|
/// storage/cache
|
||||||
Set<Widget> _mounts = {};
|
Set<ValueKey<String>> _mounts = {};
|
||||||
|
|
||||||
Query({
|
Query({
|
||||||
required this.queryKey,
|
required this.queryKey,
|
||||||
@@ -123,20 +123,20 @@ class Query<T extends Object, Outside> extends ChangeNotifier {
|
|||||||
|
|
||||||
// all methods
|
// all methods
|
||||||
|
|
||||||
void mount(Widget widget) {
|
void mount(ValueKey<String> uKey) {
|
||||||
_mounts.add(widget);
|
_mounts.add(uKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
void unmount(Widget widget) {
|
void unmount(ValueKey<String> uKey) {
|
||||||
if (_mounts.length == 1) {
|
if (_mounts.length == 1) {
|
||||||
Future.delayed(_cacheTime, () {
|
Future.delayed(_cacheTime, () {
|
||||||
_mounts.remove(widget);
|
_mounts.remove(uKey);
|
||||||
// for letting know QueryBowl that this one's time has come for
|
// for letting know QueryBowl that this one's time has come for
|
||||||
// getting crushed
|
// getting crushed
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
_mounts.remove(widget);
|
_mounts.remove(uKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ class QueryBowl extends InheritedWidget {
|
|||||||
required Outside externalData,
|
required Outside externalData,
|
||||||
final QueryListener<T>? onData,
|
final QueryListener<T>? onData,
|
||||||
final QueryListener<dynamic>? onError,
|
final QueryListener<dynamic>? onError,
|
||||||
Widget? mount,
|
required ValueKey<String> key,
|
||||||
}) async {
|
}) async {
|
||||||
final prevQuery =
|
final prevQuery =
|
||||||
_queries.firstWhereOrNull((q) => q.queryKey == options.queryKey);
|
_queries.firstWhereOrNull((q) => q.queryKey == options.queryKey);
|
||||||
@@ -174,7 +174,7 @@ class QueryBowl extends InheritedWidget {
|
|||||||
// changed
|
// changed
|
||||||
final hasExternalDataChanged =
|
final hasExternalDataChanged =
|
||||||
prevQuery.prevUsedExternalData != externalData;
|
prevQuery.prevUsedExternalData != externalData;
|
||||||
if (mount != null) prevQuery.mount(mount);
|
prevQuery.mount(key);
|
||||||
if (onData != null) prevQuery.onDataListeners.add(onData);
|
if (onData != null) prevQuery.onDataListeners.add(onData);
|
||||||
if (onError != null) prevQuery.onErrorListeners.add(onError);
|
if (onError != null) prevQuery.onErrorListeners.add(onError);
|
||||||
if (!prevQuery.hasData || hasExternalDataChanged) {
|
if (!prevQuery.hasData || hasExternalDataChanged) {
|
||||||
@@ -192,34 +192,56 @@ class QueryBowl extends InheritedWidget {
|
|||||||
onData: onData,
|
onData: onData,
|
||||||
onError: onError,
|
onError: onError,
|
||||||
);
|
);
|
||||||
if (mount != null) query.mount(mount);
|
query.mount(key);
|
||||||
_addQuery<T, Outside>(query);
|
_addQuery<T, Outside>(query);
|
||||||
return await query.fetch();
|
return await query.fetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
void addMutation<T extends Object, V>(
|
Query<T, Outside> addQuery<T extends Object, Outside>(
|
||||||
MutationJob<T, V> options, {
|
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<T>? onData,
|
||||||
final MutationListener<dynamic>? onError,
|
final MutationListener<dynamic>? onError,
|
||||||
final MutationListener<V>? onMutate,
|
final MutationListener<V>? onMutate,
|
||||||
Widget? mount,
|
required ValueKey<String> key,
|
||||||
}) {
|
}) {
|
||||||
final prevMutation = _mutations.firstWhereOrNull(
|
final prevMutation = _mutations.firstWhereOrNull(
|
||||||
(mutation) => mutation.mutationKey == options.mutationKey);
|
(prevMutation) => prevMutation.mutationKey == mutation.mutationKey);
|
||||||
if (prevMutation != null && prevMutation is Mutation<T, V>) {
|
if (prevMutation != null && prevMutation is Mutation<T, V>) {
|
||||||
if (onData != null) prevMutation.onDataListeners.add(onData);
|
if (onData != null) prevMutation.onDataListeners.add(onData);
|
||||||
if (onError != null) prevMutation.onErrorListeners.add(onError);
|
if (onError != null) prevMutation.onErrorListeners.add(onError);
|
||||||
if (onMutate != null) prevMutation.onMutateListeners.add(onMutate);
|
if (onMutate != null) prevMutation.onMutateListeners.add(onMutate);
|
||||||
if (mount != null) prevMutation.mount(mount);
|
prevMutation.mount(key);
|
||||||
|
return prevMutation;
|
||||||
} else {
|
} else {
|
||||||
final mutation = Mutation.fromOptions(
|
mutation.mount(key);
|
||||||
options,
|
|
||||||
onData: onData,
|
|
||||||
onError: onError,
|
|
||||||
onMutate: onMutate,
|
|
||||||
);
|
|
||||||
if (mount != null) mutation.mount(mount);
|
|
||||||
_addMutation(mutation);
|
_addMutation(mutation);
|
||||||
|
return mutation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:fl_query/models/query_job.dart';
|
import 'package:fl_query/models/query_job.dart';
|
||||||
import 'package:fl_query/query.dart';
|
import 'package:fl_query/query.dart';
|
||||||
import 'package:fl_query/query_bowl.dart';
|
import 'package:fl_query/query_bowl.dart';
|
||||||
|
import 'package:fl_query/utils.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
class QueryBuilder<T extends Object, Outside> extends StatefulWidget {
|
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>
|
class _QueryBuilderState<T extends Object, Outside>
|
||||||
extends State<QueryBuilder<T, Outside>> {
|
extends State<QueryBuilder<T, Outside>> {
|
||||||
late QueryBowl queryBowl;
|
late QueryBowl queryBowl;
|
||||||
|
late final ValueKey<String> uKey;
|
||||||
|
late Query<T, Outside> query;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
uKey = ValueKey<String>(uuid.v4());
|
||||||
|
query = Query<T, Outside>.fromOptions(
|
||||||
|
widget.job,
|
||||||
|
externalData: widget.externalData,
|
||||||
|
);
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
queryBowl = QueryBowl.of(context);
|
query = QueryBowl.of(context).addQuery<T, Outside>(
|
||||||
await queryBowl.fetchQuery<T, Outside>(
|
query,
|
||||||
widget.job,
|
key: uKey,
|
||||||
externalData: widget.externalData,
|
|
||||||
onData: widget.onData,
|
onData: widget.onData,
|
||||||
onError: widget.onError,
|
onError: widget.onError,
|
||||||
mount: widget,
|
|
||||||
);
|
);
|
||||||
|
await query.fetch();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant oldWidget) {
|
void didUpdateWidget(covariant oldWidget) {
|
||||||
if (oldWidget.externalData != widget.externalData) {
|
if (oldWidget.externalData != widget.externalData) {
|
||||||
queryBowl = QueryBowl.of(context);
|
QueryBowl.of(context).fetchQuery(
|
||||||
// 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(
|
|
||||||
widget.job,
|
widget.job,
|
||||||
externalData: widget.externalData,
|
externalData: widget.externalData,
|
||||||
onData: widget.onData,
|
onData: widget.onData,
|
||||||
onError: widget.onError,
|
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);
|
super.didUpdateWidget(oldWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
final query = queryBowl.getQuery(widget.job.queryKey);
|
query.unmount(uKey);
|
||||||
query?.unmount(widget);
|
if (widget.onData != null) query.onDataListeners.remove(widget.onData);
|
||||||
if (widget.onData != null) query?.onDataListeners.remove(widget.onData);
|
if (widget.onError != null) query.onErrorListeners.remove(widget.onError);
|
||||||
if (widget.onError != null) query?.onErrorListeners.remove(widget.onError);
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
queryBowl = QueryBowl.of(context);
|
queryBowl = QueryBowl.of(context);
|
||||||
final query = queryBowl.getQuery<T, Outside>(widget.job.queryKey);
|
final latestQuery = queryBowl.getQuery<T, Outside>(query.queryKey) ?? query;
|
||||||
if (query == null) return Container();
|
return widget.builder(context, latestQuery);
|
||||||
return widget.builder(context, query);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:fl_query/query.dart';
|
import 'package:fl_query/query.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
Future<void> callQueryListeners<T>(Set<QueryListener<T>> listeners, T data) {
|
Future<void> callQueryListeners<T>(Set<QueryListener<T>> listeners, T data) {
|
||||||
return Future.wait(listeners.map(
|
return Future.wait(listeners.map(
|
||||||
@@ -8,3 +9,5 @@ Future<void> callQueryListeners<T>(Set<QueryListener<T>> listeners, T data) {
|
|||||||
// await listener(data);
|
// await listener(data);
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uuid = Uuid();
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ dependencies:
|
|||||||
collection: ^1.16.0
|
collection: ^1.16.0
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
uuid: ^3.0.6
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user