diff --git a/packages/fl_query/lib/src/collections/default_configs.dart b/packages/fl_query/lib/src/collections/default_configs.dart index fa1f61a..35eb39a 100644 --- a/packages/fl_query/lib/src/collections/default_configs.dart +++ b/packages/fl_query/lib/src/collections/default_configs.dart @@ -10,6 +10,7 @@ abstract class DefaultConstants { static const RetryConfig retryConfig = RetryConfig( maxRetries: 3, retryDelay: Duration(seconds: 1), + cancelWhenOffline: true, ); static const RefreshConfig refreshConfig = RefreshConfig( @@ -17,6 +18,7 @@ abstract class DefaultConstants { refreshInterval: Duration.zero, refreshOnMount: false, refreshOnQueryFnChange: false, + refreshOnNetworkStateChange: true, ); static const Duration cacheDuration = Duration(minutes: 5); diff --git a/packages/fl_query/lib/src/collections/refresh_config.dart b/packages/fl_query/lib/src/collections/refresh_config.dart index 74e9b3d..16b75b3 100644 --- a/packages/fl_query/lib/src/collections/refresh_config.dart +++ b/packages/fl_query/lib/src/collections/refresh_config.dart @@ -7,12 +7,14 @@ class RefreshConfig { final Duration refreshInterval; final bool refreshOnMount; final bool refreshOnQueryFnChange; + final bool refreshOnNetworkStateChange; const RefreshConfig({ required this.staleDuration, required this.refreshInterval, required this.refreshOnMount, required this.refreshOnQueryFnChange, + required this.refreshOnNetworkStateChange, }); factory RefreshConfig.withDefaults( @@ -49,6 +51,7 @@ class RefreshConfig { Duration? refreshInterval, bool? refreshOnMount, bool? refreshOnQueryFnChange, + bool? refreshOnNetworkStateChange, }) { return RefreshConfig( staleDuration: staleDuration ?? this.staleDuration, @@ -56,6 +59,8 @@ class RefreshConfig { refreshOnMount: refreshOnMount ?? this.refreshOnMount, refreshOnQueryFnChange: refreshOnQueryFnChange ?? this.refreshOnQueryFnChange, + refreshOnNetworkStateChange: + refreshOnNetworkStateChange ?? this.refreshOnNetworkStateChange, ); } diff --git a/packages/fl_query/lib/src/collections/retry_config.dart b/packages/fl_query/lib/src/collections/retry_config.dart index c200ccf..7ec92ac 100644 --- a/packages/fl_query/lib/src/collections/retry_config.dart +++ b/packages/fl_query/lib/src/collections/retry_config.dart @@ -5,8 +5,13 @@ import 'package:flutter/material.dart'; class RetryConfig { final int maxRetries; final Duration retryDelay; + final bool cancelWhenOffline; - const RetryConfig({required this.maxRetries, required this.retryDelay}); + const RetryConfig({ + required this.maxRetries, + required this.retryDelay, + required this.cancelWhenOffline, + }); factory RetryConfig.withDefaults( BuildContext context, { @@ -32,10 +37,12 @@ class RetryConfig { RetryConfig copyWith({ int? maxRetries, Duration? retryDelay, + bool? cancelWhenOffline, }) { return RetryConfig( maxRetries: maxRetries ?? this.maxRetries, retryDelay: retryDelay ?? this.retryDelay, + cancelWhenOffline: cancelWhenOffline ?? this.cancelWhenOffline, ); } diff --git a/packages/fl_query/lib/src/core/client.dart b/packages/fl_query/lib/src/core/client.dart index 96ba804..00f6cf5 100644 --- a/packages/fl_query/lib/src/core/client.dart +++ b/packages/fl_query/lib/src/core/client.dart @@ -523,7 +523,7 @@ class QueryClient { required ConnectivityAdapter connectivity, String? cacheDir, }) async { - connectivity = connectivity; + QueryClient.connectivity = connectivity; await Hive.initFlutter(cacheDir); _cachePrefix = cachePrefix; await Hive.openLazyBox(queryCachePrefix); diff --git a/packages/fl_query/lib/src/core/infinite_query.dart b/packages/fl_query/lib/src/core/infinite_query.dart index 9d71706..57a4624 100644 --- a/packages/fl_query/lib/src/core/infinite_query.dart +++ b/packages/fl_query/lib/src/core/infinite_query.dart @@ -170,12 +170,37 @@ class InfiniteQuery }), ); }); + + // Listen to network changes and cancel any ongoing operations + + bool wasConnected = true; + _connectivitySubscription = QueryClient.connectivity.onConnectivityChanged + .listen((isConnected) async { + try { + if (isConnected && + !wasConnected && + refreshConfig.refreshOnNetworkStateChange) { + for (final page in state.pages) { + if (page.isStale) { + await refresh(page.page); + } + } + } else if (!isConnected && + _mutex.isLocked && + retryConfig.cancelWhenOffline) { + await _operation?.cancel(); + } + } finally { + wasConnected = isConnected; + } + }); } final _mutex = Mutex(); final LazyBox _box; final StreamController> _dataController; final StreamController> _errorController; + StreamSubscription? _connectivitySubscription; CancelableOperation? _operation; @@ -421,6 +446,15 @@ class InfiniteQuery return super.addListener(listener, fireImmediately: fireImmediately); } + @override + void dispose() { + _operation?.cancel(); + _connectivitySubscription?.cancel(); + _errorController.close(); + _dataController.close(); + super.dispose(); + } + @override operator ==(Object other) => identical(this, other) || other is InfiniteQuery && key == other.key; diff --git a/packages/fl_query/lib/src/core/mutation.dart b/packages/fl_query/lib/src/core/mutation.dart index 91a5d28..02f6417 100644 --- a/packages/fl_query/lib/src/core/mutation.dart +++ b/packages/fl_query/lib/src/core/mutation.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:async/async.dart'; import 'package:fl_query/src/collections/retry_config.dart'; +import 'package:fl_query/src/core/client.dart'; import 'package:fl_query/src/core/mixins/retryer.dart'; import 'package:mutex/mutex.dart'; import 'package:state_notifier/state_notifier.dart'; @@ -43,13 +44,38 @@ class Mutation MutationFn _mutationFn; - Mutation(this.key, MutationFn mutationFn, - {required this.retryConfig}) - : _dataController = StreamController.broadcast(), + Mutation( + this.key, + MutationFn mutationFn, { + required this.retryConfig, + }) : _dataController = StreamController.broadcast(), _errorController = StreamController.broadcast(), _mutationController = StreamController.broadcast(), _mutationFn = mutationFn, - super(MutationState()); + super(MutationState()) { + // Listen to network changes and cancel any ongoing operations + bool wasConnected = true; + _connectivitySubscription = QueryClient.connectivity.onConnectivityChanged + .listen((isConnected) async { + try { + if (!isConnected && + wasConnected && + _mutex.isLocked && + retryConfig.cancelWhenOffline) { + await _operation?.cancel(); + } + } finally { + wasConnected = isConnected; + } + }); + } + + final _mutex = Mutex(); + final StreamController _mutationController; + final StreamController _dataController; + final StreamController _errorController; + CancelableOperation? _operation; + late final StreamSubscription? _connectivitySubscription; bool get isInactive => !hasListeners; bool get isMutating => _mutex.isLocked; @@ -62,12 +88,6 @@ class Mutation Stream get errorStream => _errorController.stream; Stream get mutationStream => _mutationController.stream; - final _mutex = Mutex(); - final StreamController _mutationController; - final StreamController _dataController; - final StreamController _errorController; - CancelableOperation? _operation; - Future _operate(VariablesType variables) { return _mutex.protect(() async { state = state.copyWith(); @@ -113,6 +133,16 @@ class Mutation state = MutationState(); } + @override + void dispose() { + _operation?.cancel(); + _connectivitySubscription?.cancel(); + _dataController.close(); + _errorController.close(); + _mutationController.close(); + super.dispose(); + } + @override operator ==(Object other) { return identical(this, other) || (other is Mutation && key == other.key); diff --git a/packages/fl_query/lib/src/core/query.dart b/packages/fl_query/lib/src/core/query.dart index daab260..c4ad98c 100644 --- a/packages/fl_query/lib/src/core/query.dart +++ b/packages/fl_query/lib/src/core/query.dart @@ -92,6 +92,27 @@ class Query await refresh(); } }); + + // Listen to network changes and cancel any ongoing operations + + bool wasConnected = true; + _connectivitySubscription = QueryClient.connectivity.onConnectivityChanged + .listen((isConnected) async { + try { + if (isConnected && + !wasConnected && + state.isStale && + refreshConfig.refreshOnNetworkStateChange) { + await refresh(); + } else if (!isConnected && + _mutex.isLocked && + retryConfig.cancelWhenOffline) { + await _operation?.cancel(); + } + } finally { + wasConnected = isConnected; + } + }); } DataType? _initial; @@ -99,6 +120,7 @@ class Query final _mutex = Mutex(); final StreamController _dataController; final StreamController _errorController; + StreamSubscription? _connectivitySubscription; bool get isInitial => hasData && state.data == _initial; bool get isLoading => isInitial ? _mutex.isLocked : !hasData && !hasError; @@ -183,6 +205,15 @@ class Query return super.addListener(listener, fireImmediately: fireImmediately); } + @override + void dispose() { + _operation?.cancel(); + _connectivitySubscription?.cancel(); + _dataController.close(); + _errorController.close(); + super.dispose(); + } + @override operator ==(Object other) { return identical(this, other) || (other is Query && key == other.key);