betuko_offline_sync 3.3.0
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 #
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