Gate auto sync with network reachability checks
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
import 'package:relationship_saver/core/network/reachability/network_reachability_stub.dart'
|
||||
if (dart.library.html) 'package:relationship_saver/core/network/reachability/network_reachability_web.dart'
|
||||
if (dart.library.io) 'package:relationship_saver/core/network/reachability/network_reachability_io.dart';
|
||||
|
||||
/// Runtime network reachability probe.
|
||||
abstract class NetworkReachability {
|
||||
/// Returns `true` when network looks reachable.
|
||||
Future<bool> isReachable({Duration timeout = const Duration(seconds: 2)});
|
||||
}
|
||||
|
||||
/// Returns platform implementation for network reachability probing.
|
||||
NetworkReachability createNetworkReachability() =>
|
||||
createPlatformNetworkReachability();
|
||||
@@ -0,0 +1,25 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:relationship_saver/core/network/reachability/network_reachability.dart';
|
||||
|
||||
class IoNetworkReachability implements NetworkReachability {
|
||||
@override
|
||||
Future<bool> isReachable({
|
||||
Duration timeout = const Duration(seconds: 2),
|
||||
}) async {
|
||||
try {
|
||||
final List<InternetAddress> result = await InternetAddress.lookup(
|
||||
'example.com',
|
||||
).timeout(timeout);
|
||||
return result.isNotEmpty && result.first.rawAddress.isNotEmpty;
|
||||
} on SocketException {
|
||||
return false;
|
||||
} on TimeoutException {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NetworkReachability createPlatformNetworkReachability() =>
|
||||
IoNetworkReachability();
|
||||
@@ -0,0 +1,8 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:relationship_saver/core/network/reachability/network_reachability.dart';
|
||||
|
||||
/// Provides runtime reachability implementation used for sync triggers.
|
||||
final Provider<NetworkReachability> networkReachabilityProvider =
|
||||
Provider<NetworkReachability>((Ref ref) {
|
||||
return createNetworkReachability();
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import 'package:relationship_saver/core/network/reachability/network_reachability.dart';
|
||||
|
||||
class StubNetworkReachability implements NetworkReachability {
|
||||
@override
|
||||
Future<bool> isReachable({
|
||||
Duration timeout = const Duration(seconds: 2),
|
||||
}) async {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
NetworkReachability createPlatformNetworkReachability() =>
|
||||
StubNetworkReachability();
|
||||
@@ -0,0 +1,14 @@
|
||||
import 'package:relationship_saver/core/network/reachability/network_reachability.dart';
|
||||
|
||||
class WebNetworkReachability implements NetworkReachability {
|
||||
@override
|
||||
Future<bool> isReachable({
|
||||
Duration timeout = const Duration(seconds: 2),
|
||||
}) async {
|
||||
// Web environments vary in available probes; keep optimistic fallback.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
NetworkReachability createPlatformNetworkReachability() =>
|
||||
WebNetworkReachability();
|
||||
@@ -4,18 +4,22 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:relationship_saver/features/sync/sync_coordinator.dart';
|
||||
|
||||
typedef SyncNowRunner = Future<SyncRunResult> Function();
|
||||
typedef TriggerGate = Future<bool> Function();
|
||||
|
||||
/// Coordinates auto-triggered sync calls with cooldown and concurrency guards.
|
||||
class SyncAutoTriggerController {
|
||||
SyncAutoTriggerController({
|
||||
required SyncNowRunner syncNow,
|
||||
required DateTime Function() now,
|
||||
TriggerGate? canTrigger,
|
||||
this.minimumInterval = const Duration(minutes: 3),
|
||||
}) : _syncNow = syncNow,
|
||||
_now = now;
|
||||
_now = now,
|
||||
_canTrigger = canTrigger ?? _alwaysAllowed;
|
||||
|
||||
final SyncNowRunner _syncNow;
|
||||
final DateTime Function() _now;
|
||||
final TriggerGate _canTrigger;
|
||||
final Duration minimumInterval;
|
||||
|
||||
bool _running = false;
|
||||
@@ -45,6 +49,10 @@ class SyncAutoTriggerController {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!await _canTrigger()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_running = true;
|
||||
_lastTriggeredAt = now;
|
||||
try {
|
||||
@@ -71,4 +79,6 @@ class SyncAutoTriggerController {
|
||||
final int multiplier = 1 << cappedFailures;
|
||||
return minimumInterval * multiplier;
|
||||
}
|
||||
|
||||
static Future<bool> _alwaysAllowed() async => true;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:async';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:relationship_saver/core/config/app_config.dart';
|
||||
import 'package:relationship_saver/core/network/reachability/network_reachability_provider.dart';
|
||||
import 'package:relationship_saver/features/sync/sync_auto_trigger_controller.dart';
|
||||
import 'package:relationship_saver/features/sync/sync_coordinator.dart';
|
||||
|
||||
@@ -31,6 +32,12 @@ class _SyncBackgroundRunnerState extends ConsumerState<SyncBackgroundRunner>
|
||||
minimumInterval: Duration(
|
||||
seconds: AppConfig.backgroundSyncIntervalSeconds,
|
||||
),
|
||||
canTrigger: () async {
|
||||
if (AppConfig.useFakeBackend) {
|
||||
return true;
|
||||
}
|
||||
return ref.read(networkReachabilityProvider).isReachable();
|
||||
},
|
||||
);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
_configurePeriodicRunner();
|
||||
|
||||
Reference in New Issue
Block a user