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