diff --git a/packages/fl_query/example/lib/main.dart b/packages/fl_query/example/lib/main.dart index d668979..39ac16a 100644 --- a/packages/fl_query/example/lib/main.dart +++ b/packages/fl_query/example/lib/main.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - await QueryClient.initialize(); + await QueryClient.initialize(cachePrefix: 'fl_query_example'); runApp( QueryClientProvider( child: const MainApp(), @@ -23,7 +23,6 @@ class MainApp extends StatelessWidget { useMaterial3: true, ), title: 'FL Query Example', - // showPerformanceOverlay: true, routerConfig: router, ); } diff --git a/packages/fl_query/lib/src/core/cache.dart b/packages/fl_query/lib/src/core/cache.dart index 78894bc..e61dd76 100644 --- a/packages/fl_query/lib/src/core/cache.dart +++ b/packages/fl_query/lib/src/core/cache.dart @@ -2,9 +2,11 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:fl_query/src/collections/default_configs.dart'; +import 'package:fl_query/src/core/client.dart'; import 'package:fl_query/src/core/infinite_query.dart'; import 'package:fl_query/src/core/mutation.dart'; import 'package:fl_query/src/core/query.dart'; +import 'package:hive_flutter/hive_flutter.dart'; enum QueryCacheEventType { addQuery, @@ -113,4 +115,14 @@ class QueryCache { QueryCacheEvent(QueryCacheEventType.removeMutation, mutation), ); } + + void clear() { + _queries.clear(); + _infiniteQueries.clear(); + _mutations.clear(); + } + + LazyBox get queryHiveBox => Hive.lazyBox(QueryClient.queryCachePrefix); + LazyBox get infiniteQueryHiveBox => + Hive.lazyBox(QueryClient.infiniteQueryCachePrefix); } diff --git a/packages/fl_query/lib/src/core/client.dart b/packages/fl_query/lib/src/core/client.dart index e211dcb..fe1bc50 100644 --- a/packages/fl_query/lib/src/core/client.dart +++ b/packages/fl_query/lib/src/core/client.dart @@ -240,8 +240,19 @@ class QueryClient { ?.client; } - static Future initialize({String? cacheDir}) async { + static String _cachePrefix = 'fl_query'; + + static String get queryCachePrefix => '$_cachePrefix.cache.queries'; + static String get infiniteQueryCachePrefix => + '$_cachePrefix.cache.infinite_queries'; + + static Future initialize({ + required String cachePrefix, + String? cacheDir, + }) async { await Hive.initFlutter(cacheDir); - await Hive.openLazyBox('cache'); + _cachePrefix = cachePrefix; + await Hive.openLazyBox(queryCachePrefix); + await Hive.openLazyBox(infiniteQueryCachePrefix); } } diff --git a/packages/fl_query/lib/src/core/infinite_query.dart b/packages/fl_query/lib/src/core/infinite_query.dart index 1b32e1a..77e4b09 100644 --- a/packages/fl_query/lib/src/core/infinite_query.dart +++ b/packages/fl_query/lib/src/core/infinite_query.dart @@ -1,10 +1,7 @@ import 'dart:async'; import 'package:collection/collection.dart'; -import 'package:fl_query/src/collections/default_configs.dart'; -import 'package:fl_query/src/collections/json_config.dart'; -import 'package:fl_query/src/collections/refresh_config.dart'; -import 'package:fl_query/src/collections/retry_config.dart'; +import 'package:fl_query/fl_query.dart'; import 'package:fl_query/src/core/retryer.dart'; import 'package:fl_query/src/core/validation.dart'; import 'package:hive_flutter/adapters.dart'; @@ -121,6 +118,7 @@ class InfiniteQuery this.jsonConfig, }) : _dataController = StreamController.broadcast(), _errorController = StreamController.broadcast(), + _box = Hive.lazyBox(QueryClient.infiniteQueryCachePrefix), super(InfiniteQueryState( pages: { InfiniteQueryPage( @@ -173,7 +171,7 @@ class InfiniteQuery } final _mutex = Mutex(); - final _box = Hive.lazyBox("cache"); + final LazyBox _box; final StreamController> _dataController; final StreamController> _errorController; @@ -201,6 +199,7 @@ class InfiniteQuery Future _operation(PageType page) { return _mutex.protect(() async { + state = state.copyWith(); return await retryOperation( () => state.queryFn(page), config: retryConfig, diff --git a/packages/fl_query/lib/src/core/mutation.dart b/packages/fl_query/lib/src/core/mutation.dart index 2efa7b8..2771f21 100644 --- a/packages/fl_query/lib/src/core/mutation.dart +++ b/packages/fl_query/lib/src/core/mutation.dart @@ -77,6 +77,7 @@ class Mutation Future _operate(VariablesType variables) { return _mutex.protect(() async { + state = state.copyWith(); return await retryOperation( () { _mutationController.add(variables); diff --git a/packages/fl_query/lib/src/core/query.dart b/packages/fl_query/lib/src/core/query.dart index 61dc650..b4c074d 100644 --- a/packages/fl_query/lib/src/core/query.dart +++ b/packages/fl_query/lib/src/core/query.dart @@ -4,6 +4,7 @@ import 'package:fl_query/src/collections/default_configs.dart'; import 'package:fl_query/src/collections/json_config.dart'; import 'package:fl_query/src/collections/refresh_config.dart'; import 'package:fl_query/src/collections/retry_config.dart'; +import 'package:fl_query/src/core/client.dart'; import 'package:fl_query/src/core/retryer.dart'; import 'package:fl_query/src/core/validation.dart'; import 'package:hive_flutter/adapters.dart'; @@ -60,7 +61,7 @@ class Query this.retryConfig = DefaultConstants.retryConfig, this.refreshConfig = DefaultConstants.refreshConfig, this.jsonConfig, - }) : _box = Hive.lazyBox("cache"), + }) : _box = Hive.lazyBox(QueryClient.queryCachePrefix), _dataController = StreamController.broadcast(), _errorController = StreamController.broadcast(), _initial = initial, @@ -117,6 +118,7 @@ class Query Future _operate() { return _mutex.protect(() async { + state = state.copyWith(); return await retryOperation( state.queryFn, config: retryConfig, diff --git a/packages/fl_query/lib/src/widgets/infinite_query_builder.dart b/packages/fl_query/lib/src/widgets/infinite_query_builder.dart index c63bd69..792a3a3 100644 --- a/packages/fl_query/lib/src/widgets/infinite_query_builder.dart +++ b/packages/fl_query/lib/src/widgets/infinite_query_builder.dart @@ -6,6 +6,7 @@ import 'package:fl_query/src/collections/refresh_config.dart'; import 'package:fl_query/src/collections/retry_config.dart'; import 'package:fl_query/src/core/client.dart'; import 'package:fl_query/src/core/infinite_query.dart'; +import 'package:fl_query/src/widgets/mixins/rebuilder.dart'; import 'package:flutter/material.dart'; typedef InfiniteQueryBuilderFn = Widget Function( @@ -56,7 +57,8 @@ class InfiniteQueryBuilder } class _InfiniteQueryBuilderState - extends State> { + extends State> + with SafeRebuild { InfiniteQuery? query; VoidCallback? removeListener; @@ -64,10 +66,6 @@ class _InfiniteQueryBuilderState StreamSubscription>? dataSubscription; StreamSubscription>? errorSubscription; - void update(_) { - if (mounted) setState(() {}); - } - Future initialize() async { setState(() { query = QueryClient.of(context).createInfiniteQuery( @@ -85,7 +83,7 @@ class _InfiniteQueryBuilderState if (widget.onError != null) errorSubscription = query!.errorStream.listen(widget.onError); - removeListener = query!.addListener(update); + removeListener = query!.addListener(rebuild); }); if (widget.enabled) { await query!.fetch(); diff --git a/packages/fl_query/lib/src/widgets/mixins/rebuilder.dart b/packages/fl_query/lib/src/widgets/mixins/rebuilder.dart new file mode 100644 index 0000000..12209c0 --- /dev/null +++ b/packages/fl_query/lib/src/widgets/mixins/rebuilder.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; + +/// Guarantees that the [setState] method is called only if the widget is +/// mounted and not in the middle of a frame. If it is in the middle of a frame, +/// it will wait for the end of the frame and then call [setState]. Which ensures +/// updates are not lost/skipped. +/// +/// Shamelessly copied from [StackOverflow](https://stackoverflow.com/a/64702218/13292290) +/// +/// Thanks to [dev-aggarwal](https://stackoverflow.com/users/7061265/dev-aggarwal) +mixin SafeRebuild on State { + Future rebuild([_]) async { + if (!mounted) return; + + // if there's a current frame, + if (SchedulerBinding.instance.schedulerPhase != SchedulerPhase.idle) { + // wait for the end of that frame. + await SchedulerBinding.instance.endOfFrame; + if (!mounted) return; + } + + setState(() {}); + } +} diff --git a/packages/fl_query/lib/src/widgets/mutation_builder.dart b/packages/fl_query/lib/src/widgets/mutation_builder.dart index 9c8e2b4..6d882f0 100644 --- a/packages/fl_query/lib/src/widgets/mutation_builder.dart +++ b/packages/fl_query/lib/src/widgets/mutation_builder.dart @@ -4,6 +4,7 @@ import 'package:fl_query/src/collections/default_configs.dart'; import 'package:fl_query/src/collections/retry_config.dart'; import 'package:fl_query/src/core/client.dart'; import 'package:fl_query/src/core/mutation.dart'; +import 'package:fl_query/src/widgets/mixins/rebuilder.dart'; import 'package:flutter/material.dart'; import 'package:flutter/src/foundation/diagnostics.dart'; @@ -61,7 +62,8 @@ class MutationBuilder class _MutationBuilderState extends State< - MutationBuilder> { + MutationBuilder> + with SafeRebuild { Mutation? mutation; VoidCallback? removeListener; @@ -72,12 +74,6 @@ class _MutationBuilderState RecoveryType? recoveryData; - void update(_) { - if (mounted) { - setState(() {}); - } - } - void subscribeOnMutate() { if (widget.onMutate != null) mutationSubscription = mutation!.mutationStream.listen( @@ -135,7 +131,7 @@ class _MutationBuilderState subscribeOnMutate(); subscribeOnData(); subscribeOnError(); - removeListener = mutation!.addListener(update); + removeListener = mutation!.addListener(rebuild); }); } diff --git a/packages/fl_query/lib/src/widgets/query_builder.dart b/packages/fl_query/lib/src/widgets/query_builder.dart index 5dbe110..4432b77 100644 --- a/packages/fl_query/lib/src/widgets/query_builder.dart +++ b/packages/fl_query/lib/src/widgets/query_builder.dart @@ -6,6 +6,7 @@ import 'package:fl_query/src/collections/refresh_config.dart'; import 'package:fl_query/src/collections/retry_config.dart'; import 'package:fl_query/src/core/client.dart'; import 'package:fl_query/src/core/query.dart'; +import 'package:fl_query/src/widgets/mixins/rebuilder.dart'; import 'package:flutter/material.dart'; import 'package:flutter/src/foundation/diagnostics.dart'; @@ -54,7 +55,7 @@ class QueryBuilder extends StatefulWidget { } class _QueryBuilderState - extends State> { + extends State> with SafeRebuild { Query? query; VoidCallback? removeListener; @@ -62,10 +63,6 @@ class _QueryBuilderState StreamSubscription? dataSubscription; StreamSubscription? errorSubscription; - void update(_) { - if (mounted) setState(() {}); - } - Future initialize() async { setState(() { query = QueryClient.of(context).createQuery( @@ -82,7 +79,7 @@ class _QueryBuilderState if (widget.onData != null) errorSubscription = query!.errorStream.listen(widget.onError); - removeListener = query!.addListener(update); + removeListener = query!.addListener(rebuild); }); if (widget.enabled) { await query!.fetch();