feat: safe setState for builders, separate cache box for query and infinite query and update state before _operate to indicate loading state

This commit is contained in:
Kingkor Roy Tirtho
2023-02-23 12:37:45 +06:00
parent 842a549356
commit 68e60c1914
10 changed files with 70 additions and 30 deletions
+1 -2
View File
@@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await QueryClient.initialize(); await QueryClient.initialize(cachePrefix: 'fl_query_example');
runApp( runApp(
QueryClientProvider( QueryClientProvider(
child: const MainApp(), child: const MainApp(),
@@ -23,7 +23,6 @@ class MainApp extends StatelessWidget {
useMaterial3: true, useMaterial3: true,
), ),
title: 'FL Query Example', title: 'FL Query Example',
// showPerformanceOverlay: true,
routerConfig: router, routerConfig: router,
); );
} }
+12
View File
@@ -2,9 +2,11 @@ import 'dart:async';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:fl_query/src/collections/default_configs.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/infinite_query.dart';
import 'package:fl_query/src/core/mutation.dart'; import 'package:fl_query/src/core/mutation.dart';
import 'package:fl_query/src/core/query.dart'; import 'package:fl_query/src/core/query.dart';
import 'package:hive_flutter/hive_flutter.dart';
enum QueryCacheEventType { enum QueryCacheEventType {
addQuery, addQuery,
@@ -113,4 +115,14 @@ class QueryCache {
QueryCacheEvent(QueryCacheEventType.removeMutation, mutation), 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);
} }
+13 -2
View File
@@ -240,8 +240,19 @@ class QueryClient {
?.client; ?.client;
} }
static Future<void> 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<void> initialize({
required String cachePrefix,
String? cacheDir,
}) async {
await Hive.initFlutter(cacheDir); await Hive.initFlutter(cacheDir);
await Hive.openLazyBox('cache'); _cachePrefix = cachePrefix;
await Hive.openLazyBox(queryCachePrefix);
await Hive.openLazyBox(infiniteQueryCachePrefix);
} }
} }
@@ -1,10 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:fl_query/src/collections/default_configs.dart'; import 'package:fl_query/fl_query.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/retryer.dart'; import 'package:fl_query/src/core/retryer.dart';
import 'package:fl_query/src/core/validation.dart'; import 'package:fl_query/src/core/validation.dart';
import 'package:hive_flutter/adapters.dart'; import 'package:hive_flutter/adapters.dart';
@@ -121,6 +118,7 @@ class InfiniteQuery<DataType, ErrorType, PageType>
this.jsonConfig, this.jsonConfig,
}) : _dataController = StreamController.broadcast(), }) : _dataController = StreamController.broadcast(),
_errorController = StreamController.broadcast(), _errorController = StreamController.broadcast(),
_box = Hive.lazyBox(QueryClient.infiniteQueryCachePrefix),
super(InfiniteQueryState<DataType, ErrorType, PageType>( super(InfiniteQueryState<DataType, ErrorType, PageType>(
pages: { pages: {
InfiniteQueryPage<DataType, ErrorType, PageType>( InfiniteQueryPage<DataType, ErrorType, PageType>(
@@ -173,7 +171,7 @@ class InfiniteQuery<DataType, ErrorType, PageType>
} }
final _mutex = Mutex(); final _mutex = Mutex();
final _box = Hive.lazyBox("cache"); final LazyBox _box;
final StreamController<PageEvent<DataType, PageType>> _dataController; final StreamController<PageEvent<DataType, PageType>> _dataController;
final StreamController<PageEvent<ErrorType, PageType>> _errorController; final StreamController<PageEvent<ErrorType, PageType>> _errorController;
@@ -201,6 +199,7 @@ class InfiniteQuery<DataType, ErrorType, PageType>
Future<void> _operation(PageType page) { Future<void> _operation(PageType page) {
return _mutex.protect(() async { return _mutex.protect(() async {
state = state.copyWith();
return await retryOperation( return await retryOperation(
() => state.queryFn(page), () => state.queryFn(page),
config: retryConfig, config: retryConfig,
@@ -77,6 +77,7 @@ class Mutation<DataType, ErrorType, VariablesType>
Future<void> _operate(VariablesType variables) { Future<void> _operate(VariablesType variables) {
return _mutex.protect(() async { return _mutex.protect(() async {
state = state.copyWith();
return await retryOperation( return await retryOperation(
() { () {
_mutationController.add(variables); _mutationController.add(variables);
+3 -1
View File
@@ -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/json_config.dart';
import 'package:fl_query/src/collections/refresh_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/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/retryer.dart';
import 'package:fl_query/src/core/validation.dart'; import 'package:fl_query/src/core/validation.dart';
import 'package:hive_flutter/adapters.dart'; import 'package:hive_flutter/adapters.dart';
@@ -60,7 +61,7 @@ class Query<DataType, ErrorType>
this.retryConfig = DefaultConstants.retryConfig, this.retryConfig = DefaultConstants.retryConfig,
this.refreshConfig = DefaultConstants.refreshConfig, this.refreshConfig = DefaultConstants.refreshConfig,
this.jsonConfig, this.jsonConfig,
}) : _box = Hive.lazyBox("cache"), }) : _box = Hive.lazyBox(QueryClient.queryCachePrefix),
_dataController = StreamController<DataType>.broadcast(), _dataController = StreamController<DataType>.broadcast(),
_errorController = StreamController<ErrorType>.broadcast(), _errorController = StreamController<ErrorType>.broadcast(),
_initial = initial, _initial = initial,
@@ -117,6 +118,7 @@ class Query<DataType, ErrorType>
Future<void> _operate() { Future<void> _operate() {
return _mutex.protect(() async { return _mutex.protect(() async {
state = state.copyWith();
return await retryOperation( return await retryOperation(
state.queryFn, state.queryFn,
config: retryConfig, config: retryConfig,
@@ -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/collections/retry_config.dart';
import 'package:fl_query/src/core/client.dart'; import 'package:fl_query/src/core/client.dart';
import 'package:fl_query/src/core/infinite_query.dart'; import 'package:fl_query/src/core/infinite_query.dart';
import 'package:fl_query/src/widgets/mixins/rebuilder.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
typedef InfiniteQueryBuilderFn<DataType, ErrorType, PageType> = Widget Function( typedef InfiniteQueryBuilderFn<DataType, ErrorType, PageType> = Widget Function(
@@ -56,7 +57,8 @@ class InfiniteQueryBuilder<DataType, ErrorType, PageType>
} }
class _InfiniteQueryBuilderState<DataType, ErrorType, PageType> class _InfiniteQueryBuilderState<DataType, ErrorType, PageType>
extends State<InfiniteQueryBuilder<DataType, ErrorType, PageType>> { extends State<InfiniteQueryBuilder<DataType, ErrorType, PageType>>
with SafeRebuild {
InfiniteQuery<DataType, ErrorType, PageType>? query; InfiniteQuery<DataType, ErrorType, PageType>? query;
VoidCallback? removeListener; VoidCallback? removeListener;
@@ -64,10 +66,6 @@ class _InfiniteQueryBuilderState<DataType, ErrorType, PageType>
StreamSubscription<PageEvent<DataType, PageType>>? dataSubscription; StreamSubscription<PageEvent<DataType, PageType>>? dataSubscription;
StreamSubscription<PageEvent<ErrorType, PageType>>? errorSubscription; StreamSubscription<PageEvent<ErrorType, PageType>>? errorSubscription;
void update(_) {
if (mounted) setState(() {});
}
Future<void> initialize() async { Future<void> initialize() async {
setState(() { setState(() {
query = QueryClient.of(context).createInfiniteQuery( query = QueryClient.of(context).createInfiniteQuery(
@@ -85,7 +83,7 @@ class _InfiniteQueryBuilderState<DataType, ErrorType, PageType>
if (widget.onError != null) if (widget.onError != null)
errorSubscription = query!.errorStream.listen(widget.onError); errorSubscription = query!.errorStream.listen(widget.onError);
removeListener = query!.addListener(update); removeListener = query!.addListener(rebuild);
}); });
if (widget.enabled) { if (widget.enabled) {
await query!.fetch(); await query!.fetch();
@@ -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<T extends StatefulWidget> on State<T> {
Future<void> 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(() {});
}
}
@@ -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/collections/retry_config.dart';
import 'package:fl_query/src/core/client.dart'; import 'package:fl_query/src/core/client.dart';
import 'package:fl_query/src/core/mutation.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/material.dart';
import 'package:flutter/src/foundation/diagnostics.dart'; import 'package:flutter/src/foundation/diagnostics.dart';
@@ -61,7 +62,8 @@ class MutationBuilder<DataType, ErrorType, VariablesType, RecoveryType>
class _MutationBuilderState<DataType, ErrorType, VariablesType, RecoveryType> class _MutationBuilderState<DataType, ErrorType, VariablesType, RecoveryType>
extends State< extends State<
MutationBuilder<DataType, ErrorType, VariablesType, RecoveryType>> { MutationBuilder<DataType, ErrorType, VariablesType, RecoveryType>>
with SafeRebuild {
Mutation<DataType, ErrorType, VariablesType>? mutation; Mutation<DataType, ErrorType, VariablesType>? mutation;
VoidCallback? removeListener; VoidCallback? removeListener;
@@ -72,12 +74,6 @@ class _MutationBuilderState<DataType, ErrorType, VariablesType, RecoveryType>
RecoveryType? recoveryData; RecoveryType? recoveryData;
void update(_) {
if (mounted) {
setState(() {});
}
}
void subscribeOnMutate() { void subscribeOnMutate() {
if (widget.onMutate != null) if (widget.onMutate != null)
mutationSubscription = mutation!.mutationStream.listen( mutationSubscription = mutation!.mutationStream.listen(
@@ -135,7 +131,7 @@ class _MutationBuilderState<DataType, ErrorType, VariablesType, RecoveryType>
subscribeOnMutate(); subscribeOnMutate();
subscribeOnData(); subscribeOnData();
subscribeOnError(); subscribeOnError();
removeListener = mutation!.addListener(update); removeListener = mutation!.addListener(rebuild);
}); });
} }
@@ -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/collections/retry_config.dart';
import 'package:fl_query/src/core/client.dart'; import 'package:fl_query/src/core/client.dart';
import 'package:fl_query/src/core/query.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/material.dart';
import 'package:flutter/src/foundation/diagnostics.dart'; import 'package:flutter/src/foundation/diagnostics.dart';
@@ -54,7 +55,7 @@ class QueryBuilder<DataType, ErrorType> extends StatefulWidget {
} }
class _QueryBuilderState<DataType, ErrorType> class _QueryBuilderState<DataType, ErrorType>
extends State<QueryBuilder<DataType, ErrorType>> { extends State<QueryBuilder<DataType, ErrorType>> with SafeRebuild {
Query<DataType, ErrorType>? query; Query<DataType, ErrorType>? query;
VoidCallback? removeListener; VoidCallback? removeListener;
@@ -62,10 +63,6 @@ class _QueryBuilderState<DataType, ErrorType>
StreamSubscription<DataType>? dataSubscription; StreamSubscription<DataType>? dataSubscription;
StreamSubscription<ErrorType>? errorSubscription; StreamSubscription<ErrorType>? errorSubscription;
void update(_) {
if (mounted) setState(() {});
}
Future<void> initialize() async { Future<void> initialize() async {
setState(() { setState(() {
query = QueryClient.of(context).createQuery( query = QueryClient.of(context).createQuery(
@@ -82,7 +79,7 @@ class _QueryBuilderState<DataType, ErrorType>
if (widget.onData != null) if (widget.onData != null)
errorSubscription = query!.errorStream.listen(widget.onError); errorSubscription = query!.errorStream.listen(widget.onError);
removeListener = query!.addListener(update); removeListener = query!.addListener(rebuild);
}); });
if (widget.enabled) { if (widget.enabled) {
await query!.fetch(); await query!.fetch();