offline_sync_helper 1.0.1
offline_sync_helper: ^1.0.1 copied to clipboard
A fully customizable offline-first sync engine for Flutter apps with support for Hive, Drift, and custom storage adapters.
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:offline_sync_helper/offline_sync_helper.dart';
import 'package:offline_sync_helper/adapters/hive_adapter.dart';
import 'package:offline_sync_helper/core/remote_sync_service.dart';
part 'user.g.dart'; //this file is auto-generated by the `build_runner` package
//command to generate .g.dart : flutter pub run build_runner build
// 1. ------------------ Model ------------------
@HiveType(typeId: 0)
class User extends HiveObject {
@HiveField(0)
final int? id;
@HiveField(1)
final String username;
@HiveField(2)
final String email;
@HiveField(3)
final String? localKey;
User({this.id, required this.username, required this.email, this.localKey});
Map<String, dynamic> toJson() => {
'id': id,
'username': username,
'email': email,
};
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] is int ? json['id'] : int.tryParse(json['id']?.toString() ?? ''),
username: json['username'] ?? '',
email: json['email'] ?? '',
);
}
User copyWith({int? id, String? username, String? email, String? localKey}) {
return User(
id: id ?? this.id,
username: username ?? this.username,
email: email ?? this.email,
localKey: localKey ?? this.localKey,
);
}
}
// 2. ------------------ Sync Helper ------------------
class UserSyncHelper {
static final _helper = OfflineSyncHelper.instance<User>();
Future<void> save(User user) => _helper.save(model: user);
Future<void> update(User user) => _helper.update(model: user);
Future<void> delete(User user) => _helper.delete(model: user);
Future<List<User>> getAll() => _helper.getPendingChanges();
Future syncNow() => _helper.syncNow();
}
// 3. ------------------ Main App Entry ------------------
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter();
Hive.registerAdapter(UserAdapter());
await OfflineSyncHelper.initialize<User>(
localAdapter: HiveAdapter<User>(
'users',
deserializer: (map) => User.fromJson(map),
serializer: (model) => model.toJson(),
keyGenerator: (model) =>
model.id?.toString() ?? DateTime.now().millisecondsSinceEpoch.toString(),
storeAsMap: true,
),
remoteSyncService: HttpSyncService<User>(
'https://65e550f43070132b3b25d599.mockapi.io/Faculty',
(user) => user.toJson(),
),
config: const OfflineSyncHelperConfig(
autoSyncOnConnectivityChange: false, //set true for auto sync
enableConnectivityWatcher: true,
),
);
runApp(const MaterialApp(
debugShowCheckedModeBanner: false,
home: UserListScreen(),
));
}
// 4. ------------------ User List Screen ------------------
class UserListScreen extends StatefulWidget {
const UserListScreen({super.key});
@override
State<UserListScreen> createState() => _UserListScreenState();
}
class _UserListScreenState extends State<UserListScreen> {
final _helper = UserSyncHelper();
List<User> _users = [];
@override
void initState() {
super.initState();
_loadUsers();
}
Future<void> _loadUsers() async {
final users = await _helper.getAll();
setState(() => _users = users);
}
void _editUser(User user) async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (_) => UserAddEditScreen(user: user),
),
);
_loadUsers();
}
void _addUser() async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const UserAddEditScreen(),
),
);
_loadUsers();
}
void _deleteUser(User user) async {
await _helper.delete(user);
_loadUsers();
}
void _syncNow() async {
final result = await _helper.syncNow();
_loadUsers();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
result.success
? "Sync successfully."
: "Sync failed: ${result.error ?? "Unknown error"}",
),
backgroundColor: result.success ? Colors.green : Colors.red,
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Users'),
actions: [
IconButton(
icon: const Icon(Icons.sync),
onPressed: _syncNow,
tooltip: 'Sync Now',
),
],
),
body: _users.isEmpty
? Center(child: Text("No Users added to local yet"),)
: ListView.builder(
itemCount: _users.length,
itemBuilder: (context, index) {
final user = _users[index];
return ListTile(
title: Text(user.username),
subtitle: Text(user.email),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.edit),
onPressed: () => _editUser(user),
tooltip: 'Edit',
),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () => _deleteUser(user),
tooltip: 'Delete',
),
],
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: _addUser,
tooltip: 'Add User',
child: const Icon(Icons.add),
),
);
}
}
// 5. ------------------ Add/Edit Screen ------------------
class UserAddEditScreen extends StatefulWidget {
final User? user;
const UserAddEditScreen({super.key, this.user});
@override
State<UserAddEditScreen> createState() => _UserAddEditScreenState();
}
class _UserAddEditScreenState extends State<UserAddEditScreen> {
final _usernameController = TextEditingController();
final _emailController = TextEditingController();
final _helper = UserSyncHelper();
@override
void initState() {
super.initState();
if (widget.user != null) {
_usernameController.text = widget.user!.username;
_emailController.text = widget.user!.email;
}
}
void _save() async {
final username = _usernameController.text.trim();
final email = _emailController.text.trim();
if (widget.user != null) {
final updated = widget.user!.copyWith(username: username, email: email);
await _helper.update(updated);
} else {
final user = User(username: username, email: email);
await _helper.save(user);
}
Navigator.pop(context);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.user == null ? 'Add User' : 'Edit User')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
controller: _usernameController,
decoration: const InputDecoration(labelText: 'Username'),
),
TextField(
controller: _emailController,
decoration: const InputDecoration(labelText: 'Email'),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _save,
child: Text(widget.user == null ? 'Save' : 'Update'),
),
],
),
),
);
}
}