fero_sync 0.4.9
fero_sync: ^0.4.9 copied to clipboard
Fero - Flutter Sync Orchestration SDK by StarsGathered
import 'dart:async';
import 'package:fero_sync/fero_sync.dart';
import 'package:fero_sync/core/models/sync_checkpoint.dart';
import 'package:fero_sync/core/models/sync_payload.dart';
import 'package:fero_sync/core/models/syncable.dart';
import 'package:fero_sync/core/results/apply_result.dart';
import 'package:fero_sync/core/results/sync_batch_result.dart';
import 'package:fero_sync/core/sync_metadata_repo.dart';
import 'package:fero_sync/feature_sync/feature_sync_config.dart';
import 'package:fero_sync/feature_sync/feature_sync_handler.dart';
import 'package:fero_sync/initial_sync/enum/initial_sync_status.dart';
import 'package:fero_sync/initial_sync/initial_sync.dart';
import 'package:fero_sync/initial_sync/initial_sync_handler.dart';
/// ------------------
/// Entry Point
/// ------------------
Future<void> main() async {
// Local in-memory database
final localDb = <LocalMessage>[];
// Initialize FeroSync with initial and background sync handlers
final feroSync = await FeroSync.create(
metadataRepo: InMemorySyncMetaDataRepo(),
initialSyncConfigs: {
"messages": InitialSyncConfig(
handler: MessageInitialSyncHandler(localDb),
),
},
featureSyncConfigs: {
"messages": FeatureSyncConfig(
handler: MessageFeatureSyncHandler(localDb),
),
},
);
// Listen for initial sync status
feroSync.initialSyncNotifier.addListener(() {
print("📊 Initial Sync Status: ${feroSync.initialSyncNotifier.value}");
if (feroSync.initialSyncNotifier.value == InitialSyncStatus.completed) {
print(
"✅ Initial sync completed! Local DB has ${localDb.length} messages.");
}
});
// Only sync feature that need initial. if initial sync already completed, it will immediately update status notifier.
feroSync.startInitialSync();
// Listen for background/incremental sync updates
feroSync.featureSyncNotifier("messages")?.addListener(() {
print("📡 Background Sync Running...");
});
// Simulate user adding a message
addMessageFromUI("Hello, world!", localDb, feroSync);
}
/// ------------------
/// Local & Server Models
/// ------------------
class LocalMessage implements LocalItem {
@override
final String id;
final String text;
@override
final int version;
@override
final bool locallyModified;
LocalMessage({
required this.id,
required this.text,
required this.version,
this.locallyModified = false,
});
}
class ServerMessage implements ServerItem {
@override
final String id;
@override
final BigInt syncId;
final String text;
@override
final int version;
@override
final DateTime updatedAt;
ServerMessage({
required this.id,
required this.syncId,
required this.text,
required this.version,
required this.updatedAt,
});
}
/// ------------------
/// Initial Sync Handler
/// ------------------
class MessageInitialSyncHandler extends InitialSyncHandler {
final List<LocalMessage> _localDb;
MessageInitialSyncHandler(this._localDb);
@override
Future<SyncBatchResult> fetchRemoteData({
checkpoint,
required int batchSize,
}) async {
print("🌍 Fetching messages from server...");
// Simulated server messages
final serverMessages = [
ServerMessage(
id: "m1",
syncId: BigInt.from(1),
text: "Welcome 👋",
version: 1,
updatedAt: DateTime.parse("2024-01-01T00:00:00Z"),
),
];
return SyncBatchResult.success(
items: serverMessages
.map((msg) => SyncPayload<ServerItem>(data: msg))
.toList(),
checkpoint: SyncCheckpoint(
lastSyncedId: BigInt.from(1),
lastSyncedAt: "2024-01-01T00:00:00Z",
), // last synced message info for next incremental fetches
);
}
@override
Future<ApplyResult> saveToLocal(
List<SyncPayload<ServerItem>> remoteData) async {
for (final payload in remoteData) {
final server = payload.data as ServerMessage;
_localDb.add(LocalMessage(
id: server.id,
text: server.text,
version: server.version,
locallyModified: false,
));
}
print("💾 Initial messages saved locally");
return ApplyResult.success();
}
}
/// ------------------
/// Feature (Background) Sync Handler
/// ------------------
class MessageFeatureSyncHandler extends FeatureSyncHandler {
final List<LocalMessage> _localDb;
MessageFeatureSyncHandler(this._localDb);
@override
Future<List<SyncPayload<LocalItem>>> getLocallyModified(
{int batchSize = 50}) async {
final modified = _localDb
.where((m) => m.locallyModified)
.take(batchSize)
.map((m) => SyncPayload<LocalItem>(data: m))
.toList();
print("📤 Found ${modified.length} unsent messages");
return modified;
}
@override
Future<ApplyResult> pushLocalChanges(
[List<SyncPayload<LocalItem>>? localStates]) async {
print("🚀 Sending ${localStates?.length ?? 0} messages to server...");
for (final payload in localStates ?? []) {
final msg = payload.data as LocalMessage;
final index = _localDb.indexWhere((e) => e.id == msg.id);
if (index != -1) {
_localDb[index] = LocalMessage(
id: msg.id,
text: msg.text,
version: msg.version + 1,
locallyModified: false,
);
}
}
return ApplyResult.success();
}
@override
Future<SyncBatchResult> fetchRemoteChanges(
{checkpoint, required int batchSize}) async {
print("🌍 Fetching new messages from server...");
final newMessages = [
ServerMessage(
id: "m2",
syncId: BigInt.from(2),
text: "Hello from server!",
version: 1,
updatedAt: DateTime.parse("2024-01-01T00:00:00Z"),
),
];
return SyncBatchResult.success(
items: newMessages.map((e) => SyncPayload<ServerItem>(data: e)).toList(),
checkpoint: SyncCheckpoint(
lastSyncedId: BigInt.from(2),
lastSyncedAt: "2024-01-01T00:00:00Z",
), // last synced message info for next incremental fetches
);
}
@override
Future<ApplyResult> applyRemoteChanges(
List<SyncPayload<ServerItem>> remoteData) async {
for (final payload in remoteData) {
final server = payload.data as ServerMessage;
if (!_localDb.any((m) => m.id == server.id)) {
_localDb.add(LocalMessage(
id: server.id,
text: server.text,
version: server.version,
locallyModified: false,
));
}
}
print("📥 Applied ${remoteData.length} new messages locally");
return ApplyResult.success();
}
@override
Future<List<SyncPayload<LocalItem>>> getLocallyModifiedByIds(
{required List<String> ids}) async {
return _localDb
.where((m) => ids.contains(m.id))
.map((m) => SyncPayload<LocalItem>(data: m))
.toList();
}
}
/// ------------------
/// In-Memory Metadata Repository
/// ------------------
class InMemorySyncMetaDataRepo implements SyncMetaDataRepo {
final Map<String, SyncCheckpoint?> _checkpoints = {};
final Map<String, bool> _initialCompleted = {};
@override
Future<SyncCheckpoint?> getCheckpoint(String featureKey) async =>
_checkpoints[featureKey];
@override
Future<void> updateCheckpoint(
String featureKey, SyncCheckpoint? checkpoint) async =>
_checkpoints[featureKey] = checkpoint;
@override
Future<bool> isInitialSyncCompleted(String featureKey) async =>
_initialCompleted[featureKey] ?? false;
@override
Future<bool> areAllInitialSyncsCompleted(List<String> featureKeys) async =>
featureKeys.every((key) => _initialCompleted[key] ?? false);
@override
Future<void> setInitialSyncCompleted(
String featureKey, bool completed) async =>
_initialCompleted[featureKey] = completed;
}
/// ------------------
/// Helper: Add message from UI
/// ------------------
void addMessageFromUI(
String text, List<LocalMessage> localDb, FeroSync feroSync) {
final newMessage = LocalMessage(
id: DateTime.now().millisecondsSinceEpoch.toString(),
text: text,
version: 1,
locallyModified: true,
);
localDb.add(newMessage);
print("✏️ User added message locally: ${newMessage.text}");
// Force immediate sync for this feature
feroSync.syncFeature("messages", force: true);
}