drive_sync_flutter 1.0.1
drive_sync_flutter: ^1.0.1 copied to clipboard
Bidirectional file sync between device storage and Google Drive. SHA256-based change detection, pluggable conflict resolution, nested folder support.
drive_sync_flutter #
Bidirectional file sync between device storage and Google Drive, with conflict resolution.
Features #
- Push, pull, or bidirectional sync of any local directory to a Google Drive folder
- SHA256-based change detection — only transfers files that actually changed
- Conflict resolution — newerWins, localWins, remoteWins, or askUser (return conflicts to your UI)
- Nested folder paths —
apps/myapp/datacreates the full hierarchy automatically - Pluggable adapter interface — Google Drive included; implement
DriveAdapterfor iCloud, S3, etc. - No database — state tracked via a single JSON manifest file
- Platform-agnostic — sync engine uses callbacks for file I/O, no direct
dart:iodependency
Installation #
dependencies:
drive_sync_flutter:
git:
url: https://github.com/ajitgunturi/drive_sync_flutter.git
Quick Start #
import 'package:google_sign_in/google_sign_in.dart';
import 'package:drive_sync_flutter/drive_sync_flutter.dart';
// 1. Authenticate with Google
final googleSignIn = GoogleSignIn(scopes: ['https://www.googleapis.com/auth/drive']);
final account = await googleSignIn.signIn();
final authClient = GoogleAuthClient(await account!.authHeaders);
// 2. Create an adapter pointing at your Drive folder
final adapter = GoogleDriveAdapter.withPath(
httpClient: authClient,
folderPath: 'apps/myapp/backups',
);
// 3. Create a sync client
final client = DriveSyncClient(
adapter: adapter,
defaultStrategy: ConflictStrategy.newerWins,
);
// 4. Sync!
final result = await client.sync(localPath: '/path/to/local/data');
print('${result.filesUploaded} uploaded, ${result.filesDownloaded} downloaded');
API #
DriveSyncClient #
The high-level API for syncing a local directory with a remote folder.
final client = DriveSyncClient(adapter: adapter);
// Bidirectional sync (default) — new files go both ways, conflicts resolved by strategy
final result = await client.sync(localPath: '/data');
// Push only — local overwrites remote
await client.push(localPath: '/data');
// Pull only — remote overwrites local
await client.pull(localPath: '/data');
// Check what would change without syncing
final status = await client.status(localPath: '/data');
print('Pending: ${status.pendingChanges?.totalChanges ?? 0}');
GoogleDriveAdapter #
Handles Google Drive file operations. Supports nested folder paths — creates the full hierarchy if it doesn't exist.
// Single folder
final adapter = GoogleDriveAdapter(httpClient: authClient, folderName: 'Backups');
// Nested path (creates apps/ → myapp/ → data/ on Drive)
final adapter = GoogleDriveAdapter.withPath(
httpClient: authClient,
folderPath: 'apps/myapp/data',
);
GoogleAuthClient #
Convenience wrapper that injects Google auth headers into HTTP requests. Bridges google_sign_in with googleapis.
final account = await GoogleSignIn(scopes: ['drive']).signIn();
final authClient = GoogleAuthClient(await account!.authHeaders);
// Pass authClient to GoogleDriveAdapter
Conflict Resolution #
When both local and remote have modified the same file, the library picks one version — it does NOT merge content. There is no three-way merge, no content-aware diffing. It compares SHA256 checksums (to detect changes) and lastModified timestamps (to pick a winner). This works equally well for JSON, binary, or encrypted files since it never reads file contents.
| Strategy | Behavior |
|---|---|
newerWins |
Most recent lastModified wins. Ties go to local. |
localWins |
Always keep the local version (remote is overwritten). |
remoteWins |
Always keep the remote version (local is overwritten). |
askUser |
Skips the file and returns it in result.unresolvedConflicts for your UI to handle. |
Important: The losing version is overwritten. If you need to preserve both versions, use askUser and implement your own merge or backup logic.
final client = DriveSyncClient(
adapter: adapter,
defaultStrategy: ConflictStrategy.remoteWins, // plans folder: remote is truth
);
Custom Adapters #
Implement DriveAdapter to sync with any cloud provider:
class S3Adapter implements DriveAdapter {
@override
Future<void> ensureFolder() async { /* create bucket/prefix */ }
@override
Future<Map<String, RemoteFileInfo>> listFiles() async { /* list objects */ }
@override
Future<void> uploadFile(String path, List<int> content) async { /* PUT object */ }
@override
Future<List<int>> downloadFile(String path) async { /* GET object */ }
@override
Future<void> deleteFile(String path) async { /* DELETE object */ }
}
Architecture #
DriveSyncClient ← High-level API (sync/push/pull/status)
│
└── SyncEngine ← Orchestrates diff → resolve → transfer
│
├── ManifestDiffer ← Compares file states (added/modified/deleted/unchanged)
├── ConflictResolver ← Applies conflict strategy
└── DriveAdapter ← File I/O interface
│
└── GoogleDriveAdapter ← Google Drive v3 implementation
Manifest: A JSON file (.sync_manifest.json) stored alongside your local data tracks {path, sha256, lastModified} for each synced file. Only files that changed since the last sync are transferred.
Scope and Boundaries #
What this library does #
- Syncs files (any format: JSON, YAML, images, binary, encrypted blobs) between a local directory and a Google Drive folder
- Detects changes via SHA256 checksums — only transfers files that actually differ
- Resolves conflicts when the same file is modified locally and remotely
- Creates nested folder hierarchies on Drive automatically
- Tracks sync state via a local manifest file (
.sync_manifest.json)
What this library does NOT do #
- No encryption. Files are transferred as-is. If you need encryption, encrypt before syncing and decrypt after pulling. The library treats file contents as opaque bytes — change detection and conflict resolution work on raw bytes, so encrypted files sync correctly.
- No content merging. Conflict resolution picks one version (local or remote) — it never merges file contents. If both sides changed the same file, one version wins and the other is overwritten.
- No authentication. You must provide an authenticated
http.Client(e.g., viagoogle_sign_in). The library includesGoogleAuthClientas a convenience wrapper but does not manage OAuth flows, tokens, or refresh. - No background sync. Sync is triggered explicitly by your code. No periodic timers, no push notifications, no wake locks.
- No partial/resumable uploads. Files are uploaded/downloaded in full. Not suitable for files larger than ~50MB.
- No file locking or concurrency control. Designed for single-device use. If multiple devices sync the same folder simultaneously, conflicts are resolved by strategy but race conditions are possible.
- No subfolder recursion. Syncs files in a single Drive folder (flat). Nested local directories are not traversed — you manage one folder per
DriveSyncClient.
Developer responsibility #
| Concern | Who handles it |
|---|---|
| OAuth flow (sign-in, token refresh) | You — use google_sign_in or equivalent |
| Providing an authenticated HTTP client | You — wrap with GoogleAuthClient or your own |
| Encryption of sensitive data | You — encrypt before sync, decrypt after pull |
| File format and schema validation | You — library treats files as opaque bytes |
| Retry logic on network failure | You — library returns errors in SyncResult.errors |
| Background/periodic sync scheduling | You — call sync() when appropriate |
| Google Cloud project setup (OAuth consent, client IDs) | You — required for google_sign_in to work |
Library responsibility #
| Concern | Who handles it |
|---|---|
| Change detection (SHA256) | Library |
| Manifest tracking | Library |
| Conflict resolution | Library (configurable strategy) |
| Google Drive CRUD (list, upload, download, delete) | Library (GoogleDriveAdapter) |
| Folder creation on Drive | Library (nested paths supported) |
| Error reporting per file | Library (via SyncResult.errors) |
Permissions #
Your app needs:
- Google Drive scope:
https://www.googleapis.com/auth/drive(full) orhttps://www.googleapis.com/auth/drive.file(app-created files only) - Internet access
- Device storage (read/write local files)
Testing #
dart test
30 tests covering manifest diffing, conflict resolution, sync engine flows, and the full DriveSyncClient lifecycle.
License #
MIT