sync_repository 1.2.0
sync_repository: ^1.2.0 copied to clipboard
Offline-first sync layer for Flutter apps. Wraps a remote repository with a local SharedPreferences store and a SyncRepository.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:offline_first_sync/offline_first_sync.dart';
void main() {
runApp(const GenericApp());
}
class GenericApp extends StatelessWidget {
const GenericApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Offline Sync Example',
theme: ThemeData(primarySwatch: Colors.blue),
home: GenericHome(),
);
}
}
class InMemoryRepository implements OfsRepository {
final List<OfsTask> _items = [];
@override
Future<List<OfsTask>> getDailyTasks() async {
await Future.delayed(const Duration(milliseconds: 300));
return List<OfsTask>.from(_items);
}
@override
Future<void> addDaily(String title) async {
await Future.delayed(const Duration(milliseconds: 200));
_items.add(OfsTask(
id: DateTime.now().millisecondsSinceEpoch.toString(),
title: title,
dayOfWeek: Weekday.any,
isDone: false,
));
}
@override
Future<void> editDaily(String itemId, String title) async {
await Future.delayed(const Duration(milliseconds: 200));
final idx = _items.indexWhere((t) => t.id == itemId);
if (idx != -1) _items[idx] = _items[idx].copyWith(title: title);
}
@override
Future<void> deleteDaily(String itemId) async {
await Future.delayed(const Duration(milliseconds: 200));
_items.removeWhere((t) => t.id == itemId);
}
@override
Future<void> toggleDaily(String itemId, bool isDone) async {
await Future.delayed(const Duration(milliseconds: 200));
final idx = _items.indexWhere((t) => t.id == itemId);
if (idx != -1) _items[idx] = _items[idx].copyWith(isDone: isDone);
}
// Other methods not implemented for brevity
@override
Future<List<OfsTask>> getWeeklyTasks() async => [];
@override
Future<List<OfsTask>> getDeadlineTasks() async => [];
@override
Future<List<OfsTask>> getQuestTasks() async => [];
@override
Future<OfsSettings?> getSettings() async => null;
@override
Future<void> addWeekly(String title, Weekday day) async {}
@override
Future<void> addDeadline(
String title, DateTime date, TimeOfDay? time) async {}
@override
Future<void> addQuest(String title) async {}
@override
Future<void> editWeekly(String taskId, String title, Weekday day) async {}
@override
Future<void> editDeadline(
String taskId, String title, DateTime date, TimeOfDay? time) async {}
@override
Future<void> editQuest(String taskId, String title) async {}
@override
Future<void> deleteWeekly(String taskId) async {}
@override
Future<void> deleteDeadline(String taskId) async {}
@override
Future<void> deleteQuest(String taskId) async {}
@override
Future<void> toggleWeekly(String taskId, bool isDone) async {}
@override
Future<void> saveDailyOrder(List<String> orderedTaskIds) async {}
@override
Future<void> saveQuestOrder(List<String> orderedTaskIds) async {}
@override
Future<void> saveWeeklyAnyOrder(List<String> orderedTaskIds) async {}
}
class GenericHome extends StatefulWidget {
@override
State<GenericHome> createState() => _GenericHomeState();
}
class _GenericHomeState extends State<GenericHome> {
late final SharedPrefsLocalStore localStore;
late final InMemoryRepository remoteRepo;
late final SyncRepository syncRepo;
final TextEditingController inputController = TextEditingController();
List<OfsTask> items = [];
@override
void initState() {
super.initState();
localStore = SharedPrefsLocalStore();
remoteRepo = InMemoryRepository();
syncRepo = SyncRepository(remote: remoteRepo, local: localStore);
syncRepo.isSyncingNotifier.addListener(_onSyncingChanged);
_loadItems();
}
void _onSyncingChanged() {
setState(() {});
}
Future<void> _loadItems() async {
final loaded = await syncRepo.getDailyTasks();
setState(() {
items = loaded;
});
}
Future<void> _addItem(String title) async {
await syncRepo.addDaily(title);
inputController.clear();
await _loadItems();
}
Future<void> _toggleItem(OfsTask item) async {
await syncRepo.toggleDaily(item.id, !item.isDone);
await _loadItems();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Offline First Sync Example'),
actions: [
ValueListenableBuilder<bool>(
valueListenable: syncRepo.isSyncingNotifier,
builder: (context, syncing, child) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
const Text('Syncing'),
Icon(
syncing ? Icons.sync : Icons.check,
color: syncing ? Colors.orange : Colors.green,
),
],
),
);
},
),
],
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
Expanded(
child: TextField(
controller: inputController,
decoration: const InputDecoration(
labelText: 'Add an item',
),
),
),
IconButton(
icon: const Icon(Icons.add),
onPressed: () {
if (inputController.text.trim().isNotEmpty) {
_addItem(inputController.text.trim());
}
},
),
],
),
),
Expanded(
child: ListView.builder(
itemCount: items.length,
itemBuilder: (context, idx) {
final item = items[idx];
return ListTile(
title: Text(item.title),
leading: Checkbox(
value: item.isDone,
onChanged: (_) => _toggleItem(item),
),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () async {
await syncRepo.deleteDaily(item.id);
await _loadItems();
},
),
);
},
),
),
],
),
);
}
}