Flutter + Hive + Firebase Realtime Database + Connectivity Plus
This project demonstrates how to build an offline-first Flutter app using:
offline_sync_engineHive(local storage)Firebase Realtime Database(cloud backend)connectivity_plus(internet detection)
- Offline data creation, update, and deletion
- Automatic sync when internet reconnects
- Local operation tracking using Hive
- Cloud synchronization using Firebase
- Device-aware sync using UUID
Add these to pubspec.yaml:
dependencies:
flutter:
sdk: flutter
offline_sync_engine: ^2.3.0
hive: ^2.2.3
hive_flutter: ^1.1.0
firebase_core: ^2.8.0
firebase_database: ^10.0.15
connectivity_plus: ^6.1.4
uuid: ^4.1.0
dev_dependencies:
build_runner: ^2.4.8Or
Run:
flutter pub add uuid connectivity_plus firebase_database firebase_core offline_sync_engine hive hive_flutter ;
flutter pub get ;
-
Create project in Firebase Console
-
Enable Realtime Database
-
Add:
google-services.json(Android)GoogleService-Info.plist(iOS)
-
Initialize Firebase in
main.dart:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}class Note {
final String id;
final String content;
Note({required this.id, required this.content});
Map<String, dynamic> toJson() => {
'id': id,
'content': content,
};
factory Note.fromJson(Map<String, dynamic> json) {
return Note(
id: json['id'],
content: json['content'],
);
}
}class HiveDatabaseAdapter implements DatabaseAdapter {
final Box _opBox;
HiveDatabaseAdapter(this._opBox);
@override
Future<void> saveOperation(SyncOperation op) async {
await _opBox.put(op.opId, op.toJson());
}
@override
Future<List<SyncOperation>> getUnsentOperations() async {
return _opBox.values
.map((e) => SyncOperation.fromJson(Map<String, dynamic>.from(e)))
.toList();
}
@override
Future<void> markOperationSent(String opId) async {
await _opBox.delete(opId);
}
@override
Future<bool> isApplied(String opId) async {
return !_opBox.containsKey(opId);
}
@override
Future<void> applyOperation(SyncOperation operation) async {
await markOperationSent(operation.opId);
}
@override
Future<SyncRecord?> getRecord(String id) async {
return null;
}
}await Hive.initFlutter();
final opBox = await Hive.openBox('sync_operations');
final syncDbAdapter = HiveDatabaseAdapter(opBox);class FirebaseCloudAdapter implements CloudAdapter {
final DatabaseReference dbRef;
FirebaseCloudAdapter(this.dbRef);
@override
Future<void> push(List<SyncOperation> ops) async {
for (final op in ops) {
await dbRef.child('operations/${op.opId}').set(op.toJson());
}
}
@override
Future<List<SyncOperation>> pull() async {
final snapshot = await dbRef.child('operations').get();
if (!snapshot.exists) return [];
final data = Map<String, dynamic>.from(snapshot.value as Map);
return data.values
.map((e) => SyncOperation.fromJson(Map<String, dynamic>.from(e)))
.toList();
}
}final syncManager = SyncManager(
database: syncDbAdapter,
cloud: cloudAdapter,
deviceId: const Uuid().v4(),
);Connectivity().onConnectivityChanged.listen((result) async {
if (result != ConnectivityResult.none) {
await syncManager.sync();
}
});final id = const Uuid().v4();
await syncManager.createOrUpdate(id, {
'id': id,
'content': 'My offline-first note',
});await syncManager.delete(id);- Turn off internet
- Create or update notes
- Turn internet back on
- Sync runs automatically
- Verify data in Firebase Realtime Database
| Component | Responsibility |
|---|---|
| HiveDatabaseAdapter | Stores operations locally |
| FirebaseCloudAdapter | Pushes & pulls cloud data |
| SyncManager | Handles sync logic |
| Connectivity Plus | Detects network changes |
- Add proper conflict resolution
- Implement batching
- Add error handling & retries
- Secure Firebase rules
- Clean up synced operations
This example demonstrates a complete offline-first architecture using:
- Local-first writes
- Operation tracking
- Cloud synchronization
- Automatic reconnection handling
This structure is scalable and suitable for real-world apps requiring offline support.