betuko_offline_sync 3.3.0 copy "betuko_offline_sync: ^3.3.0" to clipboard
betuko_offline_sync: ^3.3.0 copied to clipboard

Ultra-simple offline-first Flutter package. Just get(), save(), and syncAll(). Automatic local storage with Hive, manual sync when you want.

Betuko Offline Sync #

pub package likes popularity

Ultra-simple offline-first package for Flutter. Your app always works, online or offline.

✨ Features #

  • πŸš€ Super Simple API - Just get(), save(), syncAll()
  • πŸ“± Always Fast - get() always returns local data instantly
  • πŸ”„ Manual Sync - User decides when to sync with syncAll()
  • ⚑ Auto Sync - Automatically syncs every 10 minutes when online
  • πŸ”Œ Reconnection Sync - Automatically syncs when internet connection is restored
  • πŸŒ™ Background Sync - Syncs every 15 min even when app is closed (Android)
  • πŸ’Ύ Auto Storage - Uses Hive for persistent local storage
  • πŸ“Š Sync Status - Know exactly what's synced and what's pending
  • πŸ”§ Debug Tools - Built-in debugging and reset utilities

πŸ“¦ Installation #

dependencies:
  betuko_offline_sync: ^3.3.0
flutter pub get

πŸš€ Quick Start #

1. Configure (once at app start) #

import 'package:betuko_offline_sync/betuko_offline_sync.dart';

void main() {
  GlobalConfig.init(
    baseUrl: 'https://your-api.com',
    token: 'your-auth-token',
  );
  runApp(MyApp());
}

2. Create a Manager #

final reports = OnlineOfflineManager(
  boxName: 'reports',
  endpoint: '/api/reports',
);

3. Use It! #

// Get data (ALWAYS returns local data - instant!)
final data = await reports.get();

// Save data (stored locally, synced later)
await reports.save({
  'title': 'My Report',
  'date': DateTime.now().toIso8601String(),
});

// Sync with server (when user wants fresh data)
await OnlineOfflineManager.syncAll();

⚑ Automatic Synchronization #

The library automatically syncs your data in two scenarios:

1. Periodic Sync (Every 10 minutes) #

When your app is online, syncAll() is automatically called every 10 minutes to keep your data fresh.

2. Reconnection Sync #

When the app detects that internet connection is restored (from offline to online), it automatically triggers syncAll() to sync any pending data.

No configuration needed! This works automatically once you create your first OnlineOfflineManager.

// Just create managers - auto-sync starts automatically!
final reports = OnlineOfflineManager(
  boxName: 'reports',
  endpoint: '/api/reports',
);

// Auto-sync will:
// - Run every 10 minutes when online
// - Run immediately when connection is restored

You can still call syncAll() manually anytime you want to force a sync.

πŸŒ™ Background Sync (Android) #

Sync your data even when the app is completely closed using WorkManager.

Setup #

1. Add Android Permission

In android/app/src/main/AndroidManifest.xml, add:

<manifest ...>
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    
    <application ...>
        <!-- Your app content -->
    </application>
</manifest>

2. Initialize in main()

import 'package:flutter/material.dart';
import 'package:betuko_offline_sync/betuko_offline_sync.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // Initialize config with background sync enabled
  await GlobalConfig.init(
    baseUrl: 'https://your-api.com',
    token: 'your-token',
    enableBackgroundSync: true,
  );
  
  // Initialize WorkManager
  await BackgroundSyncService.initialize();
  
  runApp(MyApp());
}

3. Register Managers and Start Background Sync

// Create your managers
final reports = OnlineOfflineManager(
  boxName: 'reports',
  endpoint: '/api/reports',
);

final users = OnlineOfflineManager(
  boxName: 'users', 
  endpoint: '/api/users',
);

// Register them for background sync
await BackgroundSyncService.registerManager(reports);
await BackgroundSyncService.registerManager(users);

// Start periodic sync (every 15 minutes)
await BackgroundSyncService.startPeriodicSync();

// Or schedule a sync when internet becomes available
await BackgroundSyncService.syncWhenConnected();

Background Sync API #

Method Description
initialize() Initialize WorkManager (call once in main)
registerManager(manager) Register a manager for background sync
unregisterManager(boxName) Unregister a manager
startPeriodicSync() Start periodic sync (every 15 min)
syncWhenConnected() Schedule sync when internet is available
stopPeriodicSync() Stop periodic sync
cancelAll() Cancel all background tasks
clearConfig() Clear saved config (call on logout)

Important Notes #

  • Android Only: Background sync uses WorkManager which is only available on Android
  • Minimum Interval: Android enforces a minimum of 15 minutes for periodic tasks
  • Battery Optimization: Android may delay execution to optimize battery (Doze mode)
  • Logout: Call BackgroundSyncService.clearConfig() when user logs out

Complete Example with Background Sync #

import 'package:flutter/material.dart';
import 'package:betuko_offline_sync/betuko_offline_sync.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  await GlobalConfig.init(
    baseUrl: 'https://api.example.com',
    token: 'your-token',
    enableBackgroundSync: true,
  );
  
  await BackgroundSyncService.initialize();
  
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late OnlineOfflineManager reports;
  
  @override
  void initState() {
    super.initState();
    _initManagers();
  }
  
  Future<void> _initManagers() async {
    reports = OnlineOfflineManager(
      boxName: 'reports',
      endpoint: '/api/reports',
    );
    
    // Register for background sync
    await BackgroundSyncService.registerManager(reports);
    await BackgroundSyncService.startPeriodicSync();
  }
  
  Future<void> _logout() async {
    // Stop background sync and clear config
    await BackgroundSyncService.cancelAll();
    await BackgroundSyncService.clearConfig();
    await GlobalConfig.clear();
    await OnlineOfflineManager.resetAll();
  }
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Background Sync Demo')),
        body: Center(child: Text('Your app here')),
      ),
    );
  }
}

πŸ“– API Reference #

Instance Methods #

Method Returns Description
get() List<Map> All local data
getSynced() List<Map> Only synced data
getPending() List<Map> Only pending data
getFullData() FullSyncData All data + counts
getSyncInfo() SyncInfo Just counts
save(Map data) void Save locally
delete(String id) void Delete by ID
clear() void Clear all data
reset() void Clear data + cache
dispose() void Release resources

Static Methods #

Method Returns Description
syncAll() Map<String, SyncResult> Sync all managers
getAllSyncInfo() Map<String, SyncInfo> Status of all managers
resetAll() void Reset everything
debugInfo() void Print debug info
getAllBoxesInfo() List<HiveBoxInfo> Hive boxes info
getTotalRecordCount() int Total records
getTotalPendingCount() int Total pending
deleteAllBoxes() void Delete from disk

πŸ“Š Check Sync Status #

Per Manager #

// Get full data with status
final data = await reports.getFullData();

print('Total: ${data.total}');
print('Synced: ${data.syncedCount}');
print('Pending: ${data.pendingCount}');
print('Percentage: ${data.syncPercentage}%');

// Access the actual data
for (final item in data.synced) {
  print('Synced: ${item['title']}');
}

for (final item in data.pending) {
  print('Pending: ${item['title']}');
}

All Managers #

final allStatus = await OnlineOfflineManager.getAllSyncInfo();

for (final entry in allStatus.entries) {
  print('${entry.key}: ${entry.value.synced}/${entry.value.total}');
}

πŸ”§ Debug Tools #

// Print complete debug info
await OnlineOfflineManager.debugInfo();

// Output:
// ═══════════════════════════════════════════════════════════
// πŸ“Š DEBUG INFO - OnlineOfflineManager
// ═══════════════════════════════════════════════════════════
// πŸ“¦ Managers activos: 2
//    β€’ reports: 150 registros (3 pendientes)
//    β€’ users: 50 registros (0 pendientes)
// πŸ’Ύ Boxes Hive:
//    β€’ reports: 150 registros (abierta)
//    β€’ users: 50 registros (abierta)
// βš™οΈ GlobalConfig:
//    β€’ Inicializado: true
//    β€’ BaseURL: https://api.com
// ═══════════════════════════════════════════════════════════

πŸ”„ Multiple Managers #

// Create multiple managers
final reports = OnlineOfflineManager(
  boxName: 'reports',
  endpoint: '/api/reports',
);

final users = OnlineOfflineManager(
  boxName: 'users',
  endpoint: '/api/users',
);

final products = OnlineOfflineManager(
  boxName: 'products',
  endpoint: '/api/products',
);

// Sync ALL with one call
final results = await OnlineOfflineManager.syncAll();

for (final entry in results.entries) {
  if (entry.value.success) {
    print('βœ… ${entry.key}: synced');
  } else {
    print('❌ ${entry.key}: ${entry.value.error}');
  }
}

🎯 Complete Example #

import 'package:flutter/material.dart';
import 'package:betuko_offline_sync/betuko_offline_sync.dart';

void main() {
  GlobalConfig.init(
    baseUrl: 'https://api.example.com',
    token: 'your-token',
  );
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final reports = OnlineOfflineManager(
    boxName: 'reports',
    endpoint: '/api/reports',
  );
  
  List<Map<String, dynamic>> data = [];
  bool isSyncing = false;

  @override
  void initState() {
    super.initState();
    _loadData();
  }

  Future<void> _loadData() async {
    final result = await reports.get();
    setState(() => data = result);
  }

  Future<void> _sync() async {
    setState(() => isSyncing = true);
    await OnlineOfflineManager.syncAll();
    await _loadData();
    setState(() => isSyncing = false);
  }

  Future<void> _addReport() async {
    await reports.save({
      'title': 'Report ${DateTime.now().millisecondsSinceEpoch}',
      'date': DateTime.now().toIso8601String(),
    });
    await _loadData();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Offline-First App'),
          actions: [
            IconButton(
              icon: isSyncing 
                ? SizedBox(
                    width: 20, 
                    height: 20, 
                    child: CircularProgressIndicator(color: Colors.white))
                : Icon(Icons.sync),
              onPressed: isSyncing ? null : _sync,
            ),
          ],
        ),
        body: ListView.builder(
          itemCount: data.length,
          itemBuilder: (context, index) {
            final item = data[index];
            final isSynced = item['sync'] == 'true';
            
            return ListTile(
              title: Text(item['title'] ?? 'No title'),
              trailing: Icon(
                isSynced ? Icons.cloud_done : Icons.cloud_off,
                color: isSynced ? Colors.green : Colors.orange,
              ),
            );
          },
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: _addReport,
          child: Icon(Icons.add),
        ),
      ),
    );
  }

  @override
  void dispose() {
    reports.dispose();
    super.dispose();
  }
}

πŸ” Update Token #

// After login or token refresh
GlobalConfig.updateToken('new-token');

πŸ—‘οΈ Reset Everything #

// Reset all data (useful for logout)
await OnlineOfflineManager.resetAll();

// Or delete all boxes from disk
await OnlineOfflineManager.deleteAllBoxes();

πŸ“‹ Data Classes #

SyncInfo #

class SyncInfo {
  int total;           // Total records
  int synced;          // Synced records
  int pending;         // Pending records
  double syncPercentage;  // 0-100
  bool isFullySynced;     // true if pending == 0
}

FullSyncData #

class FullSyncData {
  List<Map> all;      // All data
  List<Map> synced;   // Synced data
  List<Map> pending;  // Pending data
  int total;
  int syncedCount;
  int pendingCount;
  double syncPercentage;
  bool isFullySynced;
}

SyncResult #

class SyncResult {
  bool success;
  String? error;
}

🀝 Contributing #

Contributions are welcome! Please feel free to submit a Pull Request.

πŸ“„ License #

MIT License - see LICENSE for details.

πŸ‘¨β€πŸ’» Author #

Betuko - GitHub

2
likes
120
points
440
downloads

Publisher

unverified uploader

Weekly Downloads

Ultra-simple offline-first Flutter package. Just get(), save(), and syncAll(). Automatic local storage with Hive, manual sync when you want.

Repository (GitHub)
View/report issues

Topics

#offline-first #sync #hive #local-storage #background-sync

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

connectivity_plus, flutter, hive, hive_flutter, http, path_provider, shared_preferences, workmanager

More

Packages that depend on betuko_offline_sync