feat: refresh on network state change and cancel retry when offline
This commit is contained in:
@@ -10,6 +10,7 @@ abstract class DefaultConstants {
|
|||||||
static const RetryConfig retryConfig = RetryConfig(
|
static const RetryConfig retryConfig = RetryConfig(
|
||||||
maxRetries: 3,
|
maxRetries: 3,
|
||||||
retryDelay: Duration(seconds: 1),
|
retryDelay: Duration(seconds: 1),
|
||||||
|
cancelWhenOffline: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
static const RefreshConfig refreshConfig = RefreshConfig(
|
static const RefreshConfig refreshConfig = RefreshConfig(
|
||||||
@@ -17,6 +18,7 @@ abstract class DefaultConstants {
|
|||||||
refreshInterval: Duration.zero,
|
refreshInterval: Duration.zero,
|
||||||
refreshOnMount: false,
|
refreshOnMount: false,
|
||||||
refreshOnQueryFnChange: false,
|
refreshOnQueryFnChange: false,
|
||||||
|
refreshOnNetworkStateChange: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
static const Duration cacheDuration = Duration(minutes: 5);
|
static const Duration cacheDuration = Duration(minutes: 5);
|
||||||
|
|||||||
@@ -7,12 +7,14 @@ class RefreshConfig {
|
|||||||
final Duration refreshInterval;
|
final Duration refreshInterval;
|
||||||
final bool refreshOnMount;
|
final bool refreshOnMount;
|
||||||
final bool refreshOnQueryFnChange;
|
final bool refreshOnQueryFnChange;
|
||||||
|
final bool refreshOnNetworkStateChange;
|
||||||
|
|
||||||
const RefreshConfig({
|
const RefreshConfig({
|
||||||
required this.staleDuration,
|
required this.staleDuration,
|
||||||
required this.refreshInterval,
|
required this.refreshInterval,
|
||||||
required this.refreshOnMount,
|
required this.refreshOnMount,
|
||||||
required this.refreshOnQueryFnChange,
|
required this.refreshOnQueryFnChange,
|
||||||
|
required this.refreshOnNetworkStateChange,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory RefreshConfig.withDefaults(
|
factory RefreshConfig.withDefaults(
|
||||||
@@ -49,6 +51,7 @@ class RefreshConfig {
|
|||||||
Duration? refreshInterval,
|
Duration? refreshInterval,
|
||||||
bool? refreshOnMount,
|
bool? refreshOnMount,
|
||||||
bool? refreshOnQueryFnChange,
|
bool? refreshOnQueryFnChange,
|
||||||
|
bool? refreshOnNetworkStateChange,
|
||||||
}) {
|
}) {
|
||||||
return RefreshConfig(
|
return RefreshConfig(
|
||||||
staleDuration: staleDuration ?? this.staleDuration,
|
staleDuration: staleDuration ?? this.staleDuration,
|
||||||
@@ -56,6 +59,8 @@ class RefreshConfig {
|
|||||||
refreshOnMount: refreshOnMount ?? this.refreshOnMount,
|
refreshOnMount: refreshOnMount ?? this.refreshOnMount,
|
||||||
refreshOnQueryFnChange:
|
refreshOnQueryFnChange:
|
||||||
refreshOnQueryFnChange ?? this.refreshOnQueryFnChange,
|
refreshOnQueryFnChange ?? this.refreshOnQueryFnChange,
|
||||||
|
refreshOnNetworkStateChange:
|
||||||
|
refreshOnNetworkStateChange ?? this.refreshOnNetworkStateChange,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,13 @@ import 'package:flutter/material.dart';
|
|||||||
class RetryConfig {
|
class RetryConfig {
|
||||||
final int maxRetries;
|
final int maxRetries;
|
||||||
final Duration retryDelay;
|
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(
|
factory RetryConfig.withDefaults(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
@@ -32,10 +37,12 @@ class RetryConfig {
|
|||||||
RetryConfig copyWith({
|
RetryConfig copyWith({
|
||||||
int? maxRetries,
|
int? maxRetries,
|
||||||
Duration? retryDelay,
|
Duration? retryDelay,
|
||||||
|
bool? cancelWhenOffline,
|
||||||
}) {
|
}) {
|
||||||
return RetryConfig(
|
return RetryConfig(
|
||||||
maxRetries: maxRetries ?? this.maxRetries,
|
maxRetries: maxRetries ?? this.maxRetries,
|
||||||
retryDelay: retryDelay ?? this.retryDelay,
|
retryDelay: retryDelay ?? this.retryDelay,
|
||||||
|
cancelWhenOffline: cancelWhenOffline ?? this.cancelWhenOffline,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -523,7 +523,7 @@ class QueryClient {
|
|||||||
required ConnectivityAdapter connectivity,
|
required ConnectivityAdapter connectivity,
|
||||||
String? cacheDir,
|
String? cacheDir,
|
||||||
}) async {
|
}) async {
|
||||||
connectivity = connectivity;
|
QueryClient.connectivity = connectivity;
|
||||||
await Hive.initFlutter(cacheDir);
|
await Hive.initFlutter(cacheDir);
|
||||||
_cachePrefix = cachePrefix;
|
_cachePrefix = cachePrefix;
|
||||||
await Hive.openLazyBox(queryCachePrefix);
|
await Hive.openLazyBox(queryCachePrefix);
|
||||||
|
|||||||
@@ -170,12 +170,37 @@ class InfiniteQuery<DataType, ErrorType, PageType>
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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 _mutex = Mutex();
|
||||||
final LazyBox _box;
|
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;
|
||||||
|
StreamSubscription<bool>? _connectivitySubscription;
|
||||||
|
|
||||||
CancelableOperation<void>? _operation;
|
CancelableOperation<void>? _operation;
|
||||||
|
|
||||||
@@ -421,6 +446,15 @@ class InfiniteQuery<DataType, ErrorType, PageType>
|
|||||||
return super.addListener(listener, fireImmediately: fireImmediately);
|
return super.addListener(listener, fireImmediately: fireImmediately);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_operation?.cancel();
|
||||||
|
_connectivitySubscription?.cancel();
|
||||||
|
_errorController.close();
|
||||||
|
_dataController.close();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
operator ==(Object other) =>
|
operator ==(Object other) =>
|
||||||
identical(this, other) || other is InfiniteQuery && key == other.key;
|
identical(this, other) || other is InfiniteQuery && key == other.key;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:async/async.dart';
|
import 'package:async/async.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/mixins/retryer.dart';
|
import 'package:fl_query/src/core/mixins/retryer.dart';
|
||||||
import 'package:mutex/mutex.dart';
|
import 'package:mutex/mutex.dart';
|
||||||
import 'package:state_notifier/state_notifier.dart';
|
import 'package:state_notifier/state_notifier.dart';
|
||||||
@@ -43,13 +44,38 @@ class Mutation<DataType, ErrorType, VariablesType>
|
|||||||
|
|
||||||
MutationFn<DataType, VariablesType> _mutationFn;
|
MutationFn<DataType, VariablesType> _mutationFn;
|
||||||
|
|
||||||
Mutation(this.key, MutationFn<DataType, VariablesType> mutationFn,
|
Mutation(
|
||||||
{required this.retryConfig})
|
this.key,
|
||||||
: _dataController = StreamController.broadcast(),
|
MutationFn<DataType, VariablesType> mutationFn, {
|
||||||
|
required this.retryConfig,
|
||||||
|
}) : _dataController = StreamController.broadcast(),
|
||||||
_errorController = StreamController.broadcast(),
|
_errorController = StreamController.broadcast(),
|
||||||
_mutationController = StreamController.broadcast(),
|
_mutationController = StreamController.broadcast(),
|
||||||
_mutationFn = mutationFn,
|
_mutationFn = mutationFn,
|
||||||
super(MutationState<DataType, ErrorType, VariablesType>());
|
super(MutationState<DataType, ErrorType, VariablesType>()) {
|
||||||
|
// 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<VariablesType> _mutationController;
|
||||||
|
final StreamController<DataType> _dataController;
|
||||||
|
final StreamController<ErrorType> _errorController;
|
||||||
|
CancelableOperation<void>? _operation;
|
||||||
|
late final StreamSubscription<bool>? _connectivitySubscription;
|
||||||
|
|
||||||
bool get isInactive => !hasListeners;
|
bool get isInactive => !hasListeners;
|
||||||
bool get isMutating => _mutex.isLocked;
|
bool get isMutating => _mutex.isLocked;
|
||||||
@@ -62,12 +88,6 @@ class Mutation<DataType, ErrorType, VariablesType>
|
|||||||
Stream<ErrorType> get errorStream => _errorController.stream;
|
Stream<ErrorType> get errorStream => _errorController.stream;
|
||||||
Stream<VariablesType> get mutationStream => _mutationController.stream;
|
Stream<VariablesType> get mutationStream => _mutationController.stream;
|
||||||
|
|
||||||
final _mutex = Mutex();
|
|
||||||
final StreamController<VariablesType> _mutationController;
|
|
||||||
final StreamController<DataType> _dataController;
|
|
||||||
final StreamController<ErrorType> _errorController;
|
|
||||||
CancelableOperation<void>? _operation;
|
|
||||||
|
|
||||||
Future<void> _operate(VariablesType variables) {
|
Future<void> _operate(VariablesType variables) {
|
||||||
return _mutex.protect(() async {
|
return _mutex.protect(() async {
|
||||||
state = state.copyWith();
|
state = state.copyWith();
|
||||||
@@ -113,6 +133,16 @@ class Mutation<DataType, ErrorType, VariablesType>
|
|||||||
state = MutationState<DataType, ErrorType, VariablesType>();
|
state = MutationState<DataType, ErrorType, VariablesType>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_operation?.cancel();
|
||||||
|
_connectivitySubscription?.cancel();
|
||||||
|
_dataController.close();
|
||||||
|
_errorController.close();
|
||||||
|
_mutationController.close();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
operator ==(Object other) {
|
operator ==(Object other) {
|
||||||
return identical(this, other) || (other is Mutation && key == other.key);
|
return identical(this, other) || (other is Mutation && key == other.key);
|
||||||
|
|||||||
@@ -92,6 +92,27 @@ class Query<DataType, ErrorType>
|
|||||||
await refresh();
|
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;
|
DataType? _initial;
|
||||||
@@ -99,6 +120,7 @@ class Query<DataType, ErrorType>
|
|||||||
final _mutex = Mutex();
|
final _mutex = Mutex();
|
||||||
final StreamController<DataType> _dataController;
|
final StreamController<DataType> _dataController;
|
||||||
final StreamController<ErrorType> _errorController;
|
final StreamController<ErrorType> _errorController;
|
||||||
|
StreamSubscription<bool>? _connectivitySubscription;
|
||||||
|
|
||||||
bool get isInitial => hasData && state.data == _initial;
|
bool get isInitial => hasData && state.data == _initial;
|
||||||
bool get isLoading => isInitial ? _mutex.isLocked : !hasData && !hasError;
|
bool get isLoading => isInitial ? _mutex.isLocked : !hasData && !hasError;
|
||||||
@@ -183,6 +205,15 @@ class Query<DataType, ErrorType>
|
|||||||
return super.addListener(listener, fireImmediately: fireImmediately);
|
return super.addListener(listener, fireImmediately: fireImmediately);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_operation?.cancel();
|
||||||
|
_connectivitySubscription?.cancel();
|
||||||
|
_dataController.close();
|
||||||
|
_errorController.close();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
operator ==(Object other) {
|
operator ==(Object other) {
|
||||||
return identical(this, other) || (other is Query && key == other.key);
|
return identical(this, other) || (other is Query && key == other.key);
|
||||||
|
|||||||
Reference in New Issue
Block a user