feat: refresh on network state change and cancel retry when offline

This commit is contained in:
Kingkor Roy Tirtho
2023-06-21 22:10:39 +06:00
parent 72ca4cece4
commit 8c7b19c0e5
7 changed files with 121 additions and 12 deletions
@@ -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,
); );
} }
+1 -1
View File
@@ -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;
+40 -10
View File
@@ -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);
+31
View File
@@ -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);