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:
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user