flutter_offline_sync 0.0.1
flutter_offline_sync: ^0.0.1 copied to clipboard
A comprehensive Flutter package for offline functionality with automatic sync capabilities across all platforms.
Flutter Offline Sync #
A comprehensive Flutter package for offline functionality with automatic sync capabilities across all platforms.
Features #
- π Cross-Platform Support: iOS, Android, Web, Windows, macOS, Linux, and WASM
- πΎ Offline Storage: SQLite-based local database with automatic schema management
- π Automatic Sync: Background synchronization with configurable intervals
- π Network Detection: Real-time connectivity monitoring and status updates
- β‘ Conflict Resolution: Multiple strategies for handling data conflicts
- π± Background Sync: Sync data even when the app is in the background
- π― High Performance: Optimized for large datasets with batch processing
- π‘οΈ Type Safety: Full type safety with Dart's type system
- π Comprehensive Logging: Detailed logging for debugging and monitoring
- π§ͺ Well Tested: >90% test coverage with comprehensive test suite
Installation #
Add this to your package's pubspec.yaml
file:
dependencies:
flutter_offline_sync: ^0.0.1
Then run:
flutter pub get
Quick Start #
1. Initialize the Offline Sync Manager #
import 'package:flutter_offline_sync/flutter_offline_sync.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize the offline sync manager
await OfflineSyncManager.instance.initialize(
baseUrl: 'https://your-api.com',
autoSyncEnabled: true,
autoSyncInterval: const Duration(minutes: 5),
);
runApp(MyApp());
}
2. Create Your Data Model #
class TodoItem extends SyncEntity {
final String title;
final String description;
final bool isCompleted;
final int userId;
const TodoItem({
required super.id,
required super.createdAt,
required super.updatedAt,
required this.title,
required this.description,
required this.isCompleted,
required this.userId,
super.syncedAt,
super.isDeleted,
super.version,
super.metadata,
});
@override
TodoItem copyWith({
String? id,
DateTime? createdAt,
DateTime? updatedAt,
DateTime? syncedAt,
bool? isDeleted,
int? version,
Map<String, dynamic>? metadata,
String? title,
String? description,
bool? isCompleted,
int? userId,
}) {
return TodoItem(
id: id ?? this.id,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
syncedAt: syncedAt ?? this.syncedAt,
isDeleted: isDeleted ?? this.isDeleted,
version: version ?? this.version,
metadata: metadata ?? this.metadata,
title: title ?? this.title,
description: description ?? this.description,
isCompleted: isCompleted ?? this.isCompleted,
userId: userId ?? this.userId,
);
}
@override
Map<String, dynamic> toJson() {
return {
'id': id,
'title': title,
'description': description,
'completed': isCompleted,
'userId': userId,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
'synced_at': syncedAt?.toIso8601String(),
'is_deleted': isDeleted,
'version': version,
'metadata': metadata,
};
}
@override
factory TodoItem.fromJson(Map<String, dynamic> json) {
return TodoItem(
id: json['id']?.toString() ?? '',
title: json['title'] ?? '',
description: json['description'] ?? '',
isCompleted: json['completed'] ?? false,
userId: json['userId'] ?? 0,
createdAt: json['created_at'] != null
? DateTime.parse(json['created_at'])
: DateTime.now(),
updatedAt: json['updated_at'] != null
? DateTime.parse(json['updated_at'])
: DateTime.now(),
syncedAt: json['synced_at'] != null
? DateTime.parse(json['synced_at'])
: null,
isDeleted: json['is_deleted'] ?? false,
version: json['version'] ?? 1,
metadata: json['metadata'],
);
}
@override
String get tableName => 'todos';
@override
List<Object?> get props => [
...super.props,
title,
description,
isCompleted,
userId,
];
}
3. Register Your Entity #
// Register the entity for synchronization
OfflineSyncManager.instance.registerEntity(
'todos',
'/todos',
(json) => TodoItem.fromJson(json),
);
4. Use the Offline Sync Manager #
class TodoService {
// Save a new todo
Future<TodoItem> createTodo(String title, String description) async {
final todo = TodoItem(
id: DateTime.now().millisecondsSinceEpoch.toString(),
title: title,
description: description,
isCompleted: false,
userId: 1,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
return await OfflineSyncManager.instance.save(todo);
}
// Get all todos
Future<List<TodoItem>> getAllTodos() async {
return await OfflineSyncManager.instance.findAll<TodoItem>(
'todos',
orderBy: 'updated_at',
ascending: false,
);
}
// Update a todo
Future<TodoItem> updateTodo(TodoItem todo) async {
return await OfflineSyncManager.instance.update(todo);
}
// Delete a todo
Future<void> deleteTodo(String id) async {
await OfflineSyncManager.instance.delete(id, 'todos');
}
// Manual sync
Future<void> syncTodos() async {
await OfflineSyncManager.instance.sync();
}
}
5. Monitor Sync Status #
class SyncStatusWidget extends StatefulWidget {
@override
_SyncStatusWidgetState createState() => _SyncStatusWidgetState();
}
class _SyncStatusWidgetState extends State<SyncStatusWidget> {
SyncStatus _status = const SyncStatus(isOnline: false, isSyncing: false);
@override
void initState() {
super.initState();
// Listen to sync status changes
OfflineSyncManager.instance.statusStream.listen((status) {
setState(() {
_status = status;
});
});
}
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Sync Status'),
Row(
children: [
Icon(
_status.isOnline ? Icons.wifi : Icons.wifi_off,
color: _status.isOnline ? Colors.green : Colors.red,
),
SizedBox(width: 8),
Text(_status.isOnline ? 'Online' : 'Offline'),
Spacer(),
if (_status.isSyncing)
SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
),
],
),
if (_status.pendingCount > 0)
Text('Pending: ${_status.pendingCount}'),
if (_status.lastSyncAt != null)
Text('Last sync: ${_formatDateTime(_status.lastSyncAt!)}'),
],
),
),
);
}
String _formatDateTime(DateTime dateTime) {
return '${dateTime.day}/${dateTime.month}/${dateTime.year} ${dateTime.hour}:${dateTime.minute.toString().padLeft(2, '0')}';
}
}
Advanced Usage #
Custom Conflict Resolution #
class CustomConflictResolver implements ConflictResolver {
@override
Future<SyncEntity?> resolve(SyncConflict conflict) async {
// Implement your custom conflict resolution logic
if (conflict.conflictType == ConflictType.bothModified) {
// Use the version with the latest timestamp
if (conflict.localEntity.updatedAt.isAfter(conflict.remoteEntity.updatedAt)) {
return conflict.localEntity;
} else {
return conflict.remoteEntity;
}
}
return null;
}
@override
bool canResolve(ConflictType conflictType) {
return conflictType == ConflictType.bothModified;
}
@override
int get priority => 10; // Higher priority than default resolver
}
// Register the custom resolver
OfflineSyncManager.instance.registerConflictResolver(
'custom',
CustomConflictResolver(),
);
Configuration Options #
await OfflineSyncManager.instance.initialize(
baseUrl: 'https://your-api.com',
defaultHeaders: {
'Authorization': 'Bearer your-token',
'Content-Type': 'application/json',
},
timeout: const Duration(seconds: 30),
autoSyncEnabled: true,
autoSyncInterval: const Duration(minutes: 5),
maxRetries: 3,
batchSize: 50,
);
Raw Database Queries #
// Execute custom SQL queries
final results = await OfflineSyncManager.instance.rawQuery(
'SELECT * FROM todos WHERE is_completed = ?',
[1],
);
// Execute custom SQL commands
await OfflineSyncManager.instance.rawExecute(
'UPDATE todos SET is_completed = ? WHERE id = ?',
[1, 'todo-id'],
);
Database Transactions #
await OfflineSyncManager.instance.transaction((txn) async {
// Perform multiple operations atomically
await txn.insert('todos', todo1.toJson());
await txn.insert('todos', todo2.toJson());
await txn.update('todos', todo3.toJson(), where: 'id = ?', whereArgs: [todo3.id]);
});
Platform Support #
This package supports all Flutter platforms:
- β iOS - Full support with SQLite
- β Android - Full support with SQLite
- β Web - Full support with IndexedDB (via sqflite_common_ffi)
- β Windows - Full support with SQLite
- β macOS - Full support with SQLite
- β Linux - Full support with SQLite
- β WASM - Full support with WebAssembly
API Reference #
OfflineSyncManager #
The main class for managing offline synchronization.
Methods
initialize()
- Initialize the offline sync managerregisterEntity()
- Register an entity type for synchronizationregisterConflictResolver()
- Register a custom conflict resolversave()
- Save an entity to the local databaseupdate()
- Update an entity in the local databasedelete()
- Delete an entity from the local databasefindById()
- Find an entity by IDfindAll()
- Find all entities of a typecount()
- Count entities in a tablesync()
- Trigger manual synchronizationrawQuery()
- Execute raw SQL queriesrawExecute()
- Execute raw SQL commandstransaction()
- Execute database transactions
Properties
status
- Current sync statusstatusStream
- Stream of sync status changesisOnline
- Whether the device is onlineconnectivityStream
- Stream of connectivity changes
SyncEntity #
Abstract base class for all entities that can be synced.
Required Methods
copyWith()
- Create a copy with modified fieldstoJson()
- Convert to JSON mapfromJson()
- Create from JSON maptableName
- Database table name
Properties
id
- Unique identifiercreatedAt
- Creation timestampupdatedAt
- Last update timestampsyncedAt
- Last sync timestampisDeleted
- Soft delete flagversion
- Version number for conflict resolutionmetadata
- Custom metadata
SyncStatus #
Represents the current synchronization status.
Properties
isOnline
- Whether the device is onlineisSyncing
- Whether a sync is in progresslastSyncAt
- Timestamp of last successful syncpendingCount
- Number of pending itemsfailedCount
- Number of failed sync attemptslastError
- Last error messagesyncProgress
- Current sync progress (0.0 to 1.0)autoSyncEnabled
- Whether auto-sync is enabledsyncMode
- Current sync modenextSyncAt
- Next scheduled sync time
Contributing #
Contributions are welcome! Please feel free to submit a Pull Request.
License #
This project is licensed under the MIT License - see the LICENSE file for details.
Support #
If you encounter any issues or have questions, please file an issue on the GitHub repository.
Changelog #
See CHANGELOG.md for a list of changes and version history.