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 {
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,
);
}
+12
View File
@@ -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);
}
+13 -2
View File
@@ -240,8 +240,19 @@ class QueryClient {
?.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.openLazyBox('cache');
_cachePrefix = cachePrefix;
await Hive.openLazyBox(queryCachePrefix);
await Hive.openLazyBox(infiniteQueryCachePrefix);
}
}
@@ -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<DataType, ErrorType, PageType>
this.jsonConfig,
}) : _dataController = StreamController.broadcast(),
_errorController = StreamController.broadcast(),
_box = Hive.lazyBox(QueryClient.infiniteQueryCachePrefix),
super(InfiniteQueryState<DataType, ErrorType, PageType>(
pages: {
InfiniteQueryPage<DataType, ErrorType, PageType>(
@@ -173,7 +171,7 @@ class InfiniteQuery<DataType, ErrorType, PageType>
}
final _mutex = Mutex();
final _box = Hive.lazyBox("cache");
final LazyBox _box;
final StreamController<PageEvent<DataType, PageType>> _dataController;
final StreamController<PageEvent<ErrorType, PageType>> _errorController;
@@ -201,6 +199,7 @@ class InfiniteQuery<DataType, ErrorType, PageType>
Future<void> _operation(PageType page) {
return _mutex.protect(() async {
state = state.copyWith();
return await retryOperation(
() => state.queryFn(page),
config: retryConfig,
@@ -77,6 +77,7 @@ class Mutation<DataType, ErrorType, VariablesType>
Future<void> _operate(VariablesType variables) {
return _mutex.protect(() async {
state = state.copyWith();
return await retryOperation(
() {
_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/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<DataType, ErrorType>
this.retryConfig = DefaultConstants.retryConfig,
this.refreshConfig = DefaultConstants.refreshConfig,
this.jsonConfig,
}) : _box = Hive.lazyBox("cache"),
}) : _box = Hive.lazyBox(QueryClient.queryCachePrefix),
_dataController = StreamController<DataType>.broadcast(),
_errorController = StreamController<ErrorType>.broadcast(),
_initial = initial,
@@ -117,6 +118,7 @@ class Query<DataType, ErrorType>
Future<void> _operate() {
return _mutex.protect(() async {
state = state.copyWith();
return await retryOperation(
state.queryFn,
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/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<DataType, ErrorType, PageType> = Widget Function(
@@ -56,7 +57,8 @@ class InfiniteQueryBuilder<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;
VoidCallback? removeListener;
@@ -64,10 +66,6 @@ class _InfiniteQueryBuilderState<DataType, ErrorType, PageType>
StreamSubscription<PageEvent<DataType, PageType>>? dataSubscription;
StreamSubscription<PageEvent<ErrorType, PageType>>? errorSubscription;
void update(_) {
if (mounted) setState(() {});
}
Future<void> initialize() async {
setState(() {
query = QueryClient.of(context).createInfiniteQuery(
@@ -85,7 +83,7 @@ class _InfiniteQueryBuilderState<DataType, ErrorType, PageType>
if (widget.onError != null)
errorSubscription = query!.errorStream.listen(widget.onError);
removeListener = query!.addListener(update);
removeListener = query!.addListener(rebuild);
});
if (widget.enabled) {
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/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<DataType, ErrorType, VariablesType, RecoveryType>
class _MutationBuilderState<DataType, ErrorType, VariablesType, RecoveryType>
extends State<
MutationBuilder<DataType, ErrorType, VariablesType, RecoveryType>> {
MutationBuilder<DataType, ErrorType, VariablesType, RecoveryType>>
with SafeRebuild {
Mutation<DataType, ErrorType, VariablesType>? mutation;
VoidCallback? removeListener;
@@ -72,12 +74,6 @@ class _MutationBuilderState<DataType, ErrorType, VariablesType, RecoveryType>
RecoveryType? recoveryData;
void update(_) {
if (mounted) {
setState(() {});
}
}
void subscribeOnMutate() {
if (widget.onMutate != null)
mutationSubscription = mutation!.mutationStream.listen(
@@ -135,7 +131,7 @@ class _MutationBuilderState<DataType, ErrorType, VariablesType, RecoveryType>
subscribeOnMutate();
subscribeOnData();
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/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<DataType, ErrorType> extends StatefulWidget {
}
class _QueryBuilderState<DataType, ErrorType>
extends State<QueryBuilder<DataType, ErrorType>> {
extends State<QueryBuilder<DataType, ErrorType>> with SafeRebuild {
Query<DataType, ErrorType>? query;
VoidCallback? removeListener;
@@ -62,10 +63,6 @@ class _QueryBuilderState<DataType, ErrorType>
StreamSubscription<DataType>? dataSubscription;
StreamSubscription<ErrorType>? errorSubscription;
void update(_) {
if (mounted) setState(() {});
}
Future<void> initialize() async {
setState(() {
query = QueryClient.of(context).createQuery(
@@ -82,7 +79,7 @@ class _QueryBuilderState<DataType, ErrorType>
if (widget.onData != null)
errorSubscription = query!.errorStream.listen(widget.onError);
removeListener = query!.addListener(update);
removeListener = query!.addListener(rebuild);
});
if (widget.enabled) {
await query!.fetch();