todo-app/lib/sync/sync_state_factory_io.dart
Krzysztof kuhy Rudnicki f933b4baa4
Some checks are pending
release-apk / release (APK) (push) Waiting to run
Sync through Firebase, mirroring to GitHub, and stop re-downloading peers
Firebase becomes the primary backend when this device has been set up for it;
GitHub stays as a mirror so a device that has not moved yet still converges.
Not being set up is a normal state, not an error -- sync runs over GitHub
exactly as before, which is also the rollback.

This app hand-rolled its own pull-merge-push instead of calling syncLog, so it
had no revision tracking at all: every sync downloaded all four peer note logs
-- roughly 150 KB each -- whether or not anything had changed. It now reads one
small revision map first and skips any peer whose hash is unchanged, and
suppresses its own push when its log is unchanged. That is the single largest
traffic saving in the fleet and what keeps this inside the free tier.

sync() now takes a RemoteStore rather than a GitHubClient, so choosing a
backend is a change at the call site and nothing more.

runSync() is the one place that decides how a sync runs. The capture screen
syncs on a timer and on demand and the settings screen on a button; each used
to build its own client, so a backend change meant editing four call sites and
hoping none was missed.

Credentials are split by what is safe to publish, because this repo is public:
the Web API key and database URL ship in source (both already ship inside the
APK, and the security rules protect the data), while the account email and
password are entered per device and live in the OS keystore.

247 tests at 100% line coverage. Two assertions changed from `contains('PUT')`
to `isNotEmpty`: those tests sync twice, and the second push is now correctly
suppressed as a no-op -- that suppression is the point.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJ9sxEw3ymzXNjL7SHNpJc
2026-08-04 23:36:24 +02:00

30 lines
1.2 KiB
Dart

import 'dart:io';
import 'package:crdt_sync/crdt_sync.dart';
import 'package:crdt_sync/crdt_sync_io.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
/// File holding the revision cache, beside the note log it describes.
const kSyncStateFileName = 'sync_state.json';
/// Opens the revision cache on a `dart:io` platform (Android).
// coverage:ignore-start
// Resolves the real per-platform application-support directory, so it cannot
// run under test; [openSyncStateStoreIn] holds the logic and is covered.
Future<SyncStateStore> openSyncStateStore() async {
final dir = await getApplicationSupportDirectory();
return openSyncStateStoreIn(dir.path);
}
// coverage:ignore-end
/// Opens the revision cache rooted at [dirPath].
///
/// Lives next to the log it describes and must be cleared with it: skipping
/// an unchanged peer is only sound because that peer's records are already
/// merged into the local log, so state that outlived its log would skip peers
/// whose data had been lost.
SyncStateStore openSyncStateStoreIn(String dirPath) => PersistedSyncStateStore(
FileLogPersistence(File(p.join(dirPath, kSyncStateFileName))),
);