Appearance
Part 5: Background Sync Service
Your Linkbox pattern is strong: sync immediately, then periodic timer, pause on background/offline, resume and sync once.
Background Sync Service
dart
class BackgroundSyncService {
final ExpenseSyncService syncService;
Timer? _timer;
String? _currentUserId;
bool _paused = false;
static const _interval = Duration(seconds: 30);
BackgroundSyncService({required this.syncService});
Future<void> startSyncFor(String userId) async {
if (_currentUserId == userId && _timer != null) return;
_currentUserId = userId;
_paused = false;
await syncOnce();
_timer = Timer.periodic(_interval, (_) {
unawaited(syncOnce());
});
}
Future<void> syncOnce() async {
if (_paused || _currentUserId == null) return;
await syncService.pushUnsynced(_currentUserId!);
await syncService.pullDelta(_currentUserId!);
}
void pause() {
_paused = true;
_timer?.cancel();
_timer = null;
}
Future<void> resume() async {
if (_currentUserId == null) return;
_paused = false;
await syncOnce();
_timer ??= Timer.periodic(_interval, (_) => unawaited(syncOnce()));
}
void stop() {
_timer?.cancel();
_timer = null;
_currentUserId = null;
_paused = false;
}
}Network-aware Orchestration (Riverpod)
dart
final networkSyncOrchestratorProvider = Provider<void>((ref) {
ref.listen<AsyncValue<NetworkUiState>>(networkStatusStreamProvider, (prev, next) async {
if (!next.hasValue) return;
final userId = ref.read(authProvider).currentUser?.uid;
if (userId == null) return;
final sync = await ref.read(backgroundSyncServiceProvider.future);
final state = next.value!;
if (state.state == NetworkState.offline) {
sync.pause();
} else if (state.state == NetworkState.online) {
await sync.resume();
}
});
});