offline_sync_helper 1.0.1 copy "offline_sync_helper: ^1.0.1" to clipboard
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.

example/lib/main.dart

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'),
            ),
          ],
        ),
      ),
    );
  }
}
5
likes
120
points
27
downloads

Publisher

verified publishersdkwala.com

Weekly Downloads

A fully customizable offline-first sync engine for Flutter apps with support for Hive, Drift, and custom storage adapters.

Repository (GitHub)
View/report issues

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

connectivity_plus, flutter, hive, http, path_provider

More

Packages that depend on offline_sync_helper